Related
I am trying to create an IoC Container in C++ that resolves dependencies automatically.
For that I created a function with two variadic parameter packs that is declared like this:
template <class T, typename ... TDependencies, typename... TArgs>
void Register(std::function<std::shared_ptr<T> (std::shared_ptr<TDependencies> ...,
TArgs ...)> && pFactory)
Apparently, it seems the compiler is unable to match this when supplied with
Register<Foo, Bar>(std::function<std::shared_ptr<Foo>(std::shared_ptr<Bar>)>(
[](std::shared_ptr<Bar> bar){return std::make_shared<Foo>(bar);}));
The compile errors say
note: candidate: 'void Container::Register(std::function<std::shared_ptr<_Tp>
(std::shared_ptr<TDependencies>..., TArgs ...)>&&)
[with T = Foo; TDependencies = {Bar}; TArgs = {std::shared_ptr<Bar>}]'
Apparently, it matches std::shared_ptr<Bar> twice. How can I get the compiler not to match the shared_ptr in TArgs too?
Rather than trying to deduce TDependencies directly from the pFactory parameter type, I'd write a type trait to get the dependencies from the whole parameter pack instead. With boost::mp11:
template <class>
struct is_shared_ptr : std::false_type {};
template <class T>
struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
namespace mp11 = ::boost::mp11;
template <class... Ts>
using register_traits = mp11::mp_partition<mp11::mp_list<Ts...>, is_shared_ptr>;
template <class T, class F, class... TDependencies, class... TArgs>
void RegisterImpl(F && pFactory,
mp11::mp_list<
mp11::mp_list<std::shared_ptr<TDependencies>...>,
mp11::mp_list<TArgs...>>);
template <class T, class... Ts>
void Register(std::function<std::shared_ptr<T> (Ts...)> && pFactory)
{
return RegisterImpl<T>(
std::forward<std::function<std::shared_ptr<T> (Ts...)>>(pFactory),
register_traits<Ts...>{});
}
And to call it:
Register(std::function{[] (std::shared_ptr<Bar> bar) {
return std::make_shared<Foo>(bar);
}});
Try it on godbolt.org.
If boost::mp11 is not an option, here's how you can implement your own partition template metafunction:
template <class...>
struct list {};
namespace detail {
template <class L, template <class...> class P, class T, class F, class = void>
struct partition;
template <class Next, class... Ls,
template <class...> class P, class T, class... Fs>
struct partition<list<Next, Ls...>, P, T, list<Fs...>,
std::enable_if_t<!P<Next>::value>> :
partition<list<Ls...>, P, T, list<Fs..., Next>> {};
template <class Next, class... Ls,
template <class...> class P, class... Ts, class F>
struct partition<list<Next, Ls...>, P, list<Ts...>, F,
std::enable_if_t<P<Next>::value>> :
partition<list<Ls...>, P, list<Ts..., Next>, F> {};
template <template <class...> class P, class T, class F>
struct partition<list<>, P, T, F> { using type = list<T, F>; };
} // namespace detail
template <class L, template <class...> class P>
using partition = typename detail::partition<L, P, list<>, list<>>::type;
template <class... Ts>
using register_traits = partition<list<Ts...>, is_shared_ptr>;
template <class T, class F, class... TDependencies, class... TArgs>
void RegisterImpl(F && pFactory,
list<list<std::shared_ptr<TDependencies>...>, list<TArgs...>>);
Try it on godbolt.org.
The rest of the code will remain the same.
Suppose I have a class with the following signature:
template <typename T, typename... Args>
class A;
But how this class behaves should depend on some other parameter, let's say it's the value of T::value:
template <typename T, typename... Args, typename Enable>
class A;
template <typename T, typename... Args, typename = typename std::enable_if<T::value>::type>
class A
{
// do something
};
template <typename T, typename... Args, typename = typename std::enable_if<!T::value>::type>
class A
{
// do something else
};
int main() { return 0; }
However, this program gives the following error:
prog.cpp:6:11: error: parameter pack ‘Args’ must be at the end of the
template parameter list
class A;
I have struggled to find a good source of information on the use of enable_if to select classes with variadic templates. The only question I could find is this one:
How to use std::enable_if with variadic template
But despite the name, this question and its answers aren't much help. If someone could provide or link a guide on how this should be approached and why that would be appreciated.
First of all, what you're trying is writing multiple definitions of a class template. That's not allowed because it violates One definition rule. If you want to do conditional enabling with classes, you need specializations. Also, the compiler error message already told you, you can't have a variadic parameter pack in the middle of a parameter list.
One way to do it would be:
namespace detail {
template<typename T, typename Enable, typename... Args>
class A_impl;
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<T::value>::type, Args...> {
// code here
};
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<!T::value>::type, Args...> {
// code here
};
}
template<typename T, typename...Args>
class A : public detail::A_impl<T, void, Args...> {};
Jonathan's way is also perfectly fine if the condition is really a bool, but it might not be useful if you wish to add more specializations that each depend on several conditons.
It looks as though for your purposes you don't need to enable/disable the class, you just need a partial specialization:
template <typename T, bool B = T::value, typename... Args>
class A;
template <typename T, typename... Args>
class A<T, true, Args...>;
template <typename T, typename... Args>
class A<T, false, Args...>;
I am trying to modify the is_detected idiom to allow passing variadic arguments to it. I need this since some of my detected member functions will have user provided arguments.
So far, this is what I got working. You give the extra args to is_detected_args_v, and in theory, the template specialization would kick in and compile correctly. Thus giving std::true_type.
#include <type_traits>
#include <cstdio>
// slightly modified (and simplified) is_detected
template <template <class, class...> class Op, class T, class = void, class...>
struct is_detected_args : std::false_type {};
template <template <class, class...> class Op, class T, class... Args>
struct is_detected_args<Op, T, std::void_t<Op<T, Args...>>, Args...>
: std::true_type {};
template <template <class, class...> class Op, class T, class... Args>
inline constexpr bool is_detected_args_v
= is_detected_args<Op, T, Args...>::value;
// has_func, checks the function starts with int, and then Args&...
template <class T, class... Args>
using has_func = decltype(std::declval<T>().func(
std::declval<int>(), std::declval<Args&>()...));
// has the func
struct obj {
void func(int, double&, double&) {
printf("potato\n");
}
};
int main(int, char**) {
obj o;
if constexpr(is_detected_args_v<has_func, obj, double, double>) {
double d = 0;
double d2 = 42;
o.func(42, d, d2);
}
}
You can run the example here (tested on all 3 compilers) : https://wandbox.org/permlink/ttCmWSVl1XVZjty7
The problem is, the specialization is never chosen and the conditional is always false. My question is two-folds.
Is this even possible?
Why doesn't is_detected get specialized?
Thx
The main issue here is misunderstanding what void_t does. As a refresher, see how does void_t work?. The key idea is that the primary template has a void parameter and the specialization has some complex thing that you want to check wrapped in void_t so that it matches the primary template's parameter. That isn't happening in your example.
We can fix it in two easy steps. First, you have this type T along with Args... There isn't actually any reason to split this up, and it's easier to look at if we don't have extraneous parameters. So here's your attempt just reduced (I also gave a name to the parameter which is supposed to be void):
template <template <class...> class Op, class AlwaysVoid, class...>
struct is_detected_args : std::false_type {};
template <template <class...> class Op, class... Args>
struct is_detected_args<Op, std::void_t<Op<Args...>>, Args...>
: std::true_type {};
template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, Args...>::value;
Now it should be easier to see what's missing: the void parameter! You're not passing in a void and you need to. That's an easy fix though:
template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, void, Args...>::value;
// ~~~~~
And now it works as expected.
Cppreference also provides a complete implementation of is_detected if you want to look at that too.
Consider this fully working code:
#include <type_traits>
template <typename T, typename IndexPack> struct Make;
template <typename T, template <T...> class P, T... Indices>
struct Make<T, P<Indices...>> {
using type = P<(Indices+1)..., (-3*Indices)..., (Indices-1)...>;
};
template <int...> class Pack;
int main() {
static_assert (std::is_same<Make<int, Pack<1,2,3,4>>::type,
Pack<2,3,4,5, -3,-6,-9,-12, 0,1,2,3>>::value, "false");
}
What I actually want the output to be is
Pack<2,-3,0, 3,-6,1, 4,-9,2, 5,-12,3>
instead of Pack<2,3,4,5, -3,-6,-9,-12, 0,1,2,3>. I first tried
using type = P<(Indices+1, -3*Indices, Indices-1)...>;
but that is simply understood by the compiler to be a useless comma operator. What is the desired syntax to get what I want? If there is no such syntax, what is the cleanest way to do this, keeping in mind that using Indices 3 times is just an example (we may want to use it more than 3 times). Please don't tell me that I have to write a helper to extract the individual packs and then "interlace" all the elements. That nightmarish method cannot be the best solution (and such a solution would also only work if we knew exactly how many individual packs to extract).
Would defining
template <typename T, template <T...> class P, T I>
struct Component {
using type = P<I+1, -3*I, I-1>;
};
help somehow? Make a pack expansion on this?
Yes, you can concat recursively:
template <typename, typename, typename> struct Concat;
template <typename T, template <T...> class P, T... A, T... B>
struct Concat<T, P<A...>, P<B...>> {
using type = P<A..., B...>;
};
template <typename T, typename IndexPack> struct Make;
template <typename T, template <T...> class P, T... I, T F >
struct Make<T, P<F, I...>> {
using type = typename Concat<T,
typename Make<T, P<F>>::type,
typename Make<T, P<I...>>::type>::type;
};
template <typename T, template <T...> class P, T I>
struct Make<T, P<I>> {
using type = P<I+1, -3*I, I-1>;
};
Demo
This was inspired by Columbo's solution. It uses the pack expansion syntax that I originally sought, namely
using type = typename Merge<T, typename Component<T, P, Indices>::type...>::type;
As a result, now Make is reusable, first using Triple, and then using Quadruple, so any number of usages of Indices can be expanded simultaneously. Here Component is a template-template-template parameter passed into Make:
#include <type_traits>
template <typename T, typename... Packs> struct Merge;
template <typename T, template <T...> class P1, template <T...> class P2, T... Is, T... Js>
struct Merge<T, P1<Is...>, P2<Js...>> {
using type = P1<Is..., Js...>;
};
template <typename T, typename Pack1, typename Pack2, typename... Packs>
struct Merge<T, Pack1, Pack2, Packs...> {
using type = typename Merge<T, Pack1, typename Merge<T, Pack2, Packs...>::type>::type;
};
template <typename T, template <T...> class P, T I>
struct Triple {
using type = P<I+1, -3*I, I-1>;
};
template <typename T, template <T...> class P, T I>
struct Quadruple {
using type = P<I+1, -3*I, I-1, I>;
};
template <typename T, typename IndexPack,
template <typename U, template <U...> class P, U I> class Component> struct Make;
template <typename T, template <T...> class Z, T... Indices,
template <typename U, template <U...> class P, U I> class Component>
struct Make<T, Z<Indices...>, Component> {
using type = typename Merge<T, typename Component<T, Z, Indices>::type...>::type;
};
template <int...> class Pack;
int main() {
static_assert (std::is_same<Make<int, Pack<1,2,3,4>, Triple>::type,
Pack<2,-3,0, 3,-6,1, 4,-9,2, 5,-12,3>>::value, "false");
static_assert (std::is_same<Make<int, Pack<1,2,3,4>, Quadruple>::type,
Pack<2,-3,0,1, 3,-6,1,2, 4,-9,2,3, 5,-12,3,4>>::value, "false");
}
Suppose I have a class with the following signature:
template <typename T, typename... Args>
class A;
But how this class behaves should depend on some other parameter, let's say it's the value of T::value:
template <typename T, typename... Args, typename Enable>
class A;
template <typename T, typename... Args, typename = typename std::enable_if<T::value>::type>
class A
{
// do something
};
template <typename T, typename... Args, typename = typename std::enable_if<!T::value>::type>
class A
{
// do something else
};
int main() { return 0; }
However, this program gives the following error:
prog.cpp:6:11: error: parameter pack ‘Args’ must be at the end of the
template parameter list
class A;
I have struggled to find a good source of information on the use of enable_if to select classes with variadic templates. The only question I could find is this one:
How to use std::enable_if with variadic template
But despite the name, this question and its answers aren't much help. If someone could provide or link a guide on how this should be approached and why that would be appreciated.
First of all, what you're trying is writing multiple definitions of a class template. That's not allowed because it violates One definition rule. If you want to do conditional enabling with classes, you need specializations. Also, the compiler error message already told you, you can't have a variadic parameter pack in the middle of a parameter list.
One way to do it would be:
namespace detail {
template<typename T, typename Enable, typename... Args>
class A_impl;
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<T::value>::type, Args...> {
// code here
};
template<typename T, typename... Args>
class A_impl<T, typename std::enable_if<!T::value>::type, Args...> {
// code here
};
}
template<typename T, typename...Args>
class A : public detail::A_impl<T, void, Args...> {};
Jonathan's way is also perfectly fine if the condition is really a bool, but it might not be useful if you wish to add more specializations that each depend on several conditons.
It looks as though for your purposes you don't need to enable/disable the class, you just need a partial specialization:
template <typename T, bool B = T::value, typename... Args>
class A;
template <typename T, typename... Args>
class A<T, true, Args...>;
template <typename T, typename... Args>
class A<T, false, Args...>;