Reputation: 10926
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
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
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);
}
Upvotes: 5
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);
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