Search code examples
c++templatesc++17template-argument-deduction

Enabling automatic deduction of template argument type based on that argument's default value


Here's what I want to do:

#include <vector>

template <class ContainerType, typename ComparatorType>
void f(
    ContainerType c1,
    ComparatorType comp = 
    [](const typename ContainerType::value_type& l, const typename ContainerType::value_type& r) {return l < r;})
{
}

int main()
{
    std::vector<int> a{1, 2};
    f(a);
    return 0;
}

But it doesn't work: could not deduce template argument for 'ComparatorType'.

Using a proxy function instead of an actual default argument value works, but seems overly verbose, isn't there a better way? Not to mention it's not the same since now I can't just substitute the default comparator with my own without changing the function name in the client code.

#include <vector>

template <class ContainerType, typename ComparatorType>
void f(
    ContainerType c1,
    ComparatorType comp)
{
}

template <class ContainerType>
void f2(ContainerType c)
{
    f(c, [](const typename ContainerType::value_type& l, const typename ContainerType::value_type& r) {return l < r;});
}

int main()
{
    std::vector<int> a{1, 2};
    f2(a);
    return 0;
}

Solution

  • Template deduction is performed before default arguments are considered. Also, lambdas are not allowed to appear in unevaluated operands.

    You can first assign the default function to a variable. Then you can spell out its type. For example:

    auto default_functor = [](int x){ return x > 0; };
    
    template <typename T, typename F = decltype(default_functor)>
    auto function(T x, F f = default_functor)
    {
      return f(x);
    }
    

    Now you can use the function as usual:

    bool even(int x)
    {
      return x % 2 == 0;
    }
    
    struct Odd {
      bool operator()(int x) const
      {
        return x % 2 == 1;
      }
    };
    
    void g()
    {
      function(1); // use default functor
      function(1, even); // use a free function
      function(1, Odd{}); // use a function object
      function(1, [](int x){ return x < 0; }); // use another lambda
    }
    

    You don't need to explicitly specify the type.


    Note: According to @StoryTeller, this can lead to ODR violation if you use it in a header. In that case, you can use a named functor type:

    struct Positive {
        constexpr bool operator(int x) const
        {
            return x > 0;
        }
    };
    
    inline constexpr Positive default_functor{};
    
    template <typename T, typename F = decltype(default_functor)>
    auto function(T x, F f = default_functor)
    {
      return f(x);
    }