C++ - Function with multiple parameter packs and a std::function as argument - c++

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.

Related

How to use ... in a class template specialization?

This is an example with ... in the middle position of a template, however I cant understand what it means.
template <typename T, T f> class Invalid;
template <typename T1, typename... Args, T1 (*f)(Args...)>
class Invalid<T1 (*)(Args...), f>
{};
The following also works for me and is easy to comprehend.
template <typename... Args> class Invalid;
template <typename T1, typename... Args>
class Invalid<T1 (*)(Args...), T1>
{};
Why would I use the first one instead of the second?

Mixing void_t and variadic templates?

Consider the following code:
template <class F, class... Args, class = std::void_t<>>
struct is_invokable
: std::false_type {};
template <class F, class... Args>
struct is_invokable<F, Args..., std::void_t<std::invoke_result_t<F, Args...>>>
: std::true_type {};
The goal is to have a trait that is able to tell whether a callable of type F is invokable with arguments of type Args....
However, it fails to compile because:
error: parameter pack 'Args' must be at the end of the template parameter list
What is the (elegant) way to do this in C++17?
namespace details {
template <class F, class, class... Args>
struct is_invokable : std::false_type {};
template <class F, class... Args>
struct is_invokable<F, std::void_t<std::invoke_result_t<F, Args...>>, Args...>
: std::true_type {};
}
template <class F, class... Args>
using is_invokable=typename ::details::is_invokable<F, void, Args...>::type;
I propose an helper struct (is_invokable_h) and the use of std::tuple to wrap the Args...
Something like
#include <type_traits>
#include <utility>
template <typename, typename, typename = void>
struct is_invokable_h : std::false_type
{};
template <typename F, typename ... Args>
struct is_invokable_h<F, std::tuple<Args...>,
std::void_t<std::invoke_result_t<F, Args...>>>
: std::true_type
{};
template <typename F, typename ... Args>
struct is_invokable : is_invokable_h<F, std::tuple<Args...>>
{};
int foo (int)
{ return 0; }
int main()
{
static_assert( true == is_invokable<decltype(foo), int>{} );
static_assert( false == is_invokable<decltype(foo), int, int>{} );
}

Convert tuple to function parameters

template<typename... Args>
class SomeClass
{
using tuple_type = std::tuple<Args...>; // (ie: <bool,int,bool>)
tuple_type mytuple;
};
template<typename T, typename C, typename... I> // T is SomeClass
class SomeOtherClass
{
void fn(void(C::*f)(bool,int,bool)); // I want this
// based on the T::tuple_type but I'm not sure how.
};
I could simply use tuple_element 3 times if i knew the tuple has 3 elements only, but I don't know that.
Write a generic type trait:
template <class C, class F>
struct mem_ptr;
template <class C, class F>
using mem_ptr_t = typename mem_ptr<C, F>::type;
And specialize it for tuple:
template <class C, class... Args>
struct mem_ptr<C, std::tuple<Args...>> {
using type = void (C::*)(Args...);
};
And then use it:
void fun(mem_ptr_t<C, typename T::tuple_type> f);
This assumes you want void as the return type.
Could generalize this to splitting up the mem_ptr part from the tuple to func part:
template <class C, class F>
struct mem_ptr {
using type = F C::*;
};
template <class C, class F>
using mem_ptr_t = typename mem_ptr<C, F>::type;
template <class R, class T>
struct tuple_to_func;
template <class R, class... Args>
struct tuple_to_func<R, std::tuple<Args...>> {
using type = R(Args...);
};
template <class R, class T>
using tuple_to_func_t = typename tuple_to_func<R, T>::type;
In which case you'd want:
void fun(mem_ptr_t<C,
tuple_to_func_t<void, typename T::tuple_type>
> f);

Why does `has_construct<Alloc, T, Args...>::value` give different values on gcc (false) and clang (true)?

I am trying to implement allocate_traits, and then I am trying many methods and finally make it works well with gcc (>= 5), and the following is a class do the judgement about allocate_traits::construct
template <typename ...>
using type_helper = void;
template <typename T, typename... Args>
struct has_construct_helper {
template <typename Alloc, typename = type_helper<
decltype(declval<Alloc &>().construct
(declval<T *>(), declval<Args>()...))
>> static constexpr true_type test(Alloc, int);
template <typename Alloc> static constexpr false_type test(Alloc, ...);
};
template <typename Alloc, typename T, typename... Args>
using has_construct =
decltype(has_construct_helper<T, Args...>::test(declval<Alloc>(), 0));
And I test it with an allocator that doesn't include construct, and it works well with gcc (>= 5), but clang (>= 3.7) give me true. Is this code valid?
there is a whole file with the same error and may be simpler:
https://gist.github.com/anonymous/9a6125c4796d4c0227cb
The problem is with your type_helper alias template. There used to be an ambiguity in the standard for how to handle unused variadic template parameters. Clang decided to just leave them alone, so your decltype expression is never actually checked, so SFINAE doesn't occur.
The simple fix is to defer to another trait so that the arguments are evaluated:
template <typename...> struct voider { using type = void; };
template <typename...Ts> using type_helper = typename voider<Ts...>::type;
Here's a slightly simpler version of your code which uses the detection idiom for the SFINAE.
template <typename...> struct voider { using type = void; };
template <typename...Ts> using void_t = typename voider<Ts...>::type;
template <typename Alloc, typename T, typename, typename... Args>
struct has_construct_helper : std::false_type{};
template <typename Alloc, typename T, typename... Args>
struct has_construct_helper<Alloc, T,
void_t<decltype(declval<Alloc &>().construct
(declval<T *>(), declval<Args>()...))>,
Args...> : std::true_type
{};
template <typename Alloc, typename T, typename... Args>
using has_construct = typename has_construct_helper<Alloc, T, void, Args...>::type;
template <typename Alloc, typename T, typename... Args>
void doConstruct(std::true_type, Alloc &a, T *p, Args&&... args)
{
a.construct(p, forward<Args>(args)...);
}
template <typename Alloc, typename T, typename... Args>
void doConstruct(std::false_type, Alloc &, T *p, Args&&... args)
{
::new (static_cast<void *>(p)) T(forward<Args>(args)...);
}
template <typename Alloc>
class allocator_traits {
public:
//...
template <typename T, typename... Args> static
void construct(Alloc &a, T *p, Args&&... args)
{
doConstruct(has_construct<Alloc,T,Args...>{}, a, p, forward<Args>(args)...);
}
//...
};
Live Demo

Indices trick used for several components

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");
}