Tobias Hermann
Tobias Hermann

Reputation: 10926

function template: default first template argument to second

Is is possible to make the first template argument of a function template default to the second one if the first one is not specified?

Here is a small example:

#include <algorithm>
#include <list>
#include <vector>

template <typename ContainerOut, typename ContainerIn>
ContainerOut KeepNegatives(const ContainerIn& xs)
{
    ContainerOut result;
    auto itOut = std::inserter(result, std::end(result));
    auto isNegative = [](auto x){ return x < 0; };
    std::copy_if(std::begin(xs), std::end(xs), itOut, isNegative);
    return result;
}

int main()
{
    typedef std::vector<int> IntVector;
    typedef std::list<int> IntList;

    IntVector intVector = { 1, -2, -3, 4 };
    IntList intList = { 1, -2, -3, 4 };

    auto intVec2 = KeepNegatives<IntVector>(intList);
    auto intList2 = KeepNegatives<IntList>(intVector);
    auto intVec3 = KeepNegatives<IntVector>(intVector);
}

This works, but what I want is, that the type of the return value of KeepNegatives (i.e. ContainerOut) is the same as the type of the input value (i.e. ContainerIn) in case ContainerOut is not specified. So that the following line of code would compile (right now it does not) and return an IntVector.

    auto intVec4 = KeepNegatives(intVector);

Upvotes: 4

Views: 145

Answers (3)

T.C.
T.C.

Reputation: 137301

A better approach, taken by, e.g., std::experimental::make_array, is to not use ContainerOut as the return type directly; this allows you to specify a tag type as the default (void is a simple choice), and then compute the return type.

template <typename ContainerOut = void, typename ContainerIn,
          typename ret_t = std::conditional_t<std::is_void<ContainerOut>{},
                                              ContainerIn, ContainerOut>>
ret_t KeepNegatives(const ContainerIn& xs){
      // ...
}

Upvotes: 3

Rostislav
Rostislav

Reputation: 3977

You could simply add an overload for this special case:

template <typename ContainerIn>
ContainerIn KeepNegatives(const ContainerIn& xs)
{
    return KeepNegatives<ContainerIn, ContainerIn>(xs);
}

This, however, can cause an ambiguity in your intVec3 case. Here's one way around it:

#include <algorithm>
#include <list>
#include <vector>

template <typename ContainerOut, typename ContainerIn, 
          typename = std::enable_if_t<!std::is_same<ContainerOut, ContainerIn>::value>>
ContainerOut KeepNegatives(const ContainerIn& xs)
{
    ContainerOut result;
    auto itOut = std::inserter(result, std::end(result));
    auto isNegative = [](auto x){ return x < 0; };
    std::copy_if(std::begin(xs), std::end(xs), itOut, isNegative);
    return result;
}

template <typename ContainerIn>
ContainerIn KeepNegatives(const ContainerIn& xs)
{
    return KeepNegatives<ContainerIn, ContainerIn, void>(xs);
}


int main()
{
    typedef std::vector<int> IntVector;
    typedef std::list<int> IntList;

    IntVector intVector = { 1, -2, -3, 4 };
    IntList intList = { 1, -2, -3, 4 };

    auto intVec2 = KeepNegatives<IntVector>(intList);
    auto intList2 = KeepNegatives<IntList>(intVector);
    auto intVec3 = KeepNegatives<IntVector>(intVector);
    auto intVec4 = KeepNegatives(intVector);
}

Live

Upvotes: 5

Dimitrios Bouzas
Dimitrios Bouzas

Reputation: 42889

You could combine SFINAE with an overload as below:

template <typename ContainerOut, typename ContainerIn>
std::enable_if_t<!std::is_same<ContainerOut, ContainerIn>::value, ContainerOut>
KeepNegatives(const ContainerIn& xs) {
    ContainerOut result;
    auto itOut = std::inserter(result, std::end(result));
    auto isNegative = [](auto x) { return x < 0; };
    std::copy_if(std::begin(xs), std::end(xs), itOut, isNegative);
    return result;
}

template <typename ContainerIn>
ContainerIn
KeepNegatives(const ContainerIn& xs) {
    ContainerIn result;
    auto itOut = std::inserter(result, std::end(result));
    auto isNegative = [](auto x) { return x < 0; };
    std::copy_if(std::begin(xs), std::end(xs), itOut, isNegative);
    return result;
}

and in main:

    auto intVec2 = KeepNegatives<IntVector>(intList);
    auto intList2 = KeepNegatives<IntList>(intVector);
    auto intVec3 = KeepNegatives(intVector);

LIVE DEMO

Providing just a simple overload as:

template <typename ContainerIn>
ContainerIn KeepNegatives(const ContainerIn& xs) {...}

You'll get an ambiguous call to overloaded function, if the user provides explicitly the same template argument as:

auto intVec3 = KeepNegatives<IntVector>(intVector);

Upvotes: 1

Related Questions