c++c++11templatestemplate-specialization

Template specialization - compiler cannot divine functional argument type


I am trying to specialize a templated function with a functional argument, but the compiler is unable to infer the proper specialization. I get compiler errors unless I am explicit about the function type.

#include <functional>

int data;
using MyFunction = std::function<int()>;

template<class T> void set(const T& value)
{
    data = value;
}

template<> void set<MyFunction>(const MyFunction& f)
{
    data = f();
}

int main()
{
    set(1); // OK
    
    MyFunction f2 = []() { return 1; }; // OK
    set(f2);
    
    auto f1 = []() { return 1; }; // COMPILE ERROR
    set(f1);
    
    set([]() { return 1; }); // COMPILE ERROR

    return 0;
}

The compiler error (g++/c++11):

main.cpp: In instantiation of ‘void set(const T&) [with T = main()::<lambda()>]’:
main.cpp:26:8:   required from here
main.cpp:10:10: error: invalid user-defined conversion from ‘const main()::’ to ‘int’ [-fpermissive]
   10 |     data = value;
      |     ~~~~~^~~~~~~
main.cpp:25:15: note: candidate is: ‘constexpr main()::::operator int (*)()() const’ (near match)
   25 |     auto f1 = []() { return 1; }; // COMPILE ERROR

Example code includes a boiled-down version of my attempts. Tried C++11, 14, 17, 20 with same results. MSVC 16 and g++, similar results.

I'm hoping/expecting the compiler to correctly infer the lambda function type and pick the correct specialization.


Solution

  • I'm hoping/expecting the compiler to correctly infer the lambda function type and pick the correct specialization.

    The problem is that implicit conversions are not considered during template argument deduction.

    To solve this either just provide ordinary non-template overload instead of specialization or an alternative is to use std::is_invocable_r along with constexpr if as shown below:

    Method 1

    Here we use a non-template overload and requires(in C++20). Note that it is trivial to change(if needed) requires to SFINAE and is left as an exercise.

    template<class T> void set(const T& value) requires(!std::is_invocable_v<T>)
    {
        data = value;
    }
    //non template overload
    void set(const MyFunction& f)
    {
        data = f();
    }
    

    Method 2

    Here we use constexpr if along with std::is_invocable_v.

    template <class T> void set(T const& value) 
    { 
        if constexpr (std::is_invocable_v<T>) 
        { 
            data = value(); 
        } 
        else 
        { 
            data = value; 
        } 
    }
    
    

    Working demo