Search code examples
c++variadic-templatesstd-functiontemplate-argument-deduction

How to compare types (including ref-qualifiers) in a parameter pack and the types of std::function parameters


I'm storing an std::function inside a variadic class template (passed in the constructor of the class).

While doing so, I want to check that the types of the std::function parameters are the same as the types within the parameter pack of the class template. Here is an example:

template<typename... T>
class Foo {
public:
    explicit Foo(std::function<void(T...)> f)
        : func(std::move(f)) {
        // static_assert(...) How to formulate the static_assert here?
    }

    std::function<void(T...)> func;    
};

int main()
{
    Foo<int> fooA([](int& a){}); // does not compile 
    Foo<int&> fooB([](int a){}); // should not compile, but does
    
    Foo<int&> fooC([](int& a){}); // should compile 
}

If a class Foo<int&> is defined with a reference type, I'd like to make sure that the lambda also takes an int by reference and not by value.

The other way around (Foo<int> and lambda with an int&) already does not compile as that lambda cannot be converted to a function taking an int by value.

Using a static_assert(), I tried to ensure that the type of the parameter in the lambda matches the type given as a template parameter to Foo.

So far, I have tried unwrapping the function arguments:

template<typename... T>
struct ID {};

template<typename Func>
struct Unwrap;

template<typename R, typename... Args>
struct Unwrap<R(Args...)> {
    using ArgsType = ID<Args...>;
};

template<typename R, typename... Args>
struct Unwrap<std::function<R(Args...)>> 
    : Unwrap<R(Args...)> {};

...
static_assert(std::is_same<typename Unwrap<std::function<void(T...)>>::ArgsType, ID<T...>>::value, "");
...

Is there a way to achieve what I want? (Does it even make sense for me to think about such a check on my end, or should someone using my class Foo make sure, that he provides a lambda with the correct parameter types?)


Solution

  • If you can use C++17... what about using the CTAD defined for std::function to check that the std::function deduced for the argument of the constructor is exactly the same of func?

    I mean... what about as follows?

    template <typename... T>
    struct Foo
     {
       template <typename L>
          Foo (L && l) : func{ std::forward<L>(l) }
        {
          using T1 = decltype(func);
          using T2 = decltype(std::function{std::forward<L>(l)});
    
          static_assert( std::is_same_v<T1, T2>, "a better failure message, please" );
        }
    
       std::function<void(T...)> func;    
     };
    

    Using this Foo you get

    //Foo<int> fooA([](int& a){}); // compilation error (assigning func) 
    //Foo<int&> fooB([](int a){}); // compilation error (static_assert failure)
    
    Foo<int&> fooC([](int& a){}); // compile 
    

    --- EDIT ---

    The OP observe that

    Foo<void(int&)> fooD([](auto&) {}); would not compile

    Unfortunately, this solution impose a std::function parameters CTAD deduction, so gives a compilation error when the argument is a generic lambda.

    We can SFINAE deactivate the static_assert() in case the std::function is not deducible

    constexpr std::false_type Bar (...);
    
    template <typename L>
    constexpr auto Bar (L && l)
       -> decltype( std::function{std::forward<L>(l)}, std::true_type{} );
    
    template <typename... T>
    struct Foo
     {
       template <typename L>
          Foo (L && l) : func{ std::forward<L>(l) }
        {
          if constexpr ( decltype(Bar(std::forward<L>(l)))::value == true )
           {
             using T1 = decltype(func);
             using T2 = decltype(std::function{std::forward<L>(l)});
    
             static_assert( std::is_same_v<T1, T2>, "a better message, please" );
           }
        }
    
       std::function<void(T...)> func;    
     };
    

    So

    Foo<int &> fooF([](auto &){});
    

    become compilable.

    Continue to gives an error a generic lambda receiving a generic reference when a value is requested

    //Foo<int>   fooD([](auto &){}); // compilation error (assigning func)
    

    because remain the error assigning func.

    The problem is that, deactivating the test, the following code compile

    Foo<int &> fooE([](auto){}); // compile (!)
    

    and I don't know how to avoid it.