Violet Giraffe
Violet Giraffe

Reputation: 33579

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

Upvotes: 4

Views: 96

Answers (2)

L. F.
L. F.

Reputation: 20579

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

Upvotes: 4

without changing the function name in the client code.

You can overload function templates just fine. There is no need to use a different name.

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

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

You can't make a default function argument contribute to template argument deduction. It's not allowed because it raises some difficult to resolve questions in the deduction process.

Upvotes: 5

Related Questions