Using C++11's enable_if I want to define several specialized implementations for a function (based on the type of the parameter, say) as well as a default implementation. What is the correct way to define it?
The following example does not work as intended since the "generic" implementation is called, whatever the type T.
#include <iostream>
template<typename T, typename Enable = void>
void dummy(T t)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
std::cout << "Floating point: " << t << std::endl;
}
int main() {
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Generic: 5"
}
One solution in my minimal example consists in explicitly declaring the "generic" implementation as not for integral nor floating point types, using
std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type
This is exactly what I want to avoid, since in my real use cases there are a lot of specialized implementations and I would like to avoid a very long (error prone!) condition for the default implementation.
You can introduce a rank to give priority to some of your overloads:
template <unsigned int N>
struct rank : rank<N - 1> { };
template <>
struct rank<0> { };
You can then define your dummy overloads like this:
template<typename T>
void dummy(T t, rank<0>)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Floating point: " << t << std::endl;
}
Then, you can hide the call behind a dispatch:
template <typename T>
void dispatch(T t)
{
return dummy(t, rank<1>{});
}
Usage:
int main()
{
dispatch(5); // Print "Integral: 5"
dispatch(5.); // Print "Floating point: 5"
dispatch("hi"); // Print "Generic: hi"
}
live example on wandbox
Explanation:
Using rank introduces "priority" because implicit conversions are required to convert a rank<X> to a rank<Y> when X > Y. dispatch first tries to call dummy with rank<1>, giving priority to your constrained overloads. If enable_if fails, rank<1> is implicitly converted to rank<0> and enters the "fallback" case.
Bonus: here's a C++17 implementation using if constexpr(...).
template<typename T>
void dummy(T t)
{
if constexpr(std::is_integral_v<T>)
{
std::cout << "Integral: " << t << std::endl;
}
else if constexpr(std::is_floating_point_v<T>)
{
std::cout << "Floating point: " << t << std::endl;
}
else
{
std::cout << "Generic: " << t << std::endl;
}
}
live example on wandbox
Function cannot be partially specialized. I assume what you want to do is to prefer those overloads which contains explicit condition? One way to achieve that is by using variadic arguments ellipsis in declaration of the default function as the ellipsis function have lower priority in overload resolution order:
#include <iostream>
template<typename T>
void dummy_impl(T t, ...)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Floating point: " << t << std::endl;
}
template <class T>
void dummy(T t) {
dummy_impl(t, int{});
}
int main() {
dummy(5);
dummy(5.);
dummy("abc");
}
Output:
Integral: 5
Floating point: 5
Generic: abc
[live demo]
Another option as #doublep mention in comment is by use of structure with implementation of your function and then partially specialize it.
I would use tag dispatching like so:
namespace Details
{
namespace SupportedTypes
{
struct Integral {};
struct FloatingPoint {};
struct Generic {};
};
template <typename T, typename = void>
struct GetSupportedType
{
typedef SupportedTypes::Generic Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
{
typedef SupportedTypes::Integral Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
{
typedef SupportedTypes::FloatingPoint Type;
};
template <typename T>
void dummy(T t, SupportedTypes::Generic)
{
std::cout << "Generic: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::Integral)
{
std::cout << "Integral: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::FloatingPoint)
{
std::cout << "Floating point: " << t << std::endl;
}
} // namespace Details
And then hide the boiler plate code like so:
template <typename T>
void dummy(T t)
{
typedef typename Details::GetSupportedType< T >::Type SupportedType;
Details::dummy(t, SupportedType());
}
GetSupportedType gives you one central way to guess the actual type you are going to use, that's the one you want to specialize everytime you add a new type.
Then you just invoke the right dummy overload by providing an instance of the right tag.
Finally, invoke dummy:
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Floating point: 5"
dummy("lol"); // Print "Generic: lol"
Related
I am using both g++ 7.5.0 and clang 6.0.0 on ubuntu to try the SFINAE function of auto dispatching function call according to the method existence of an object and the result doesn't go as expected.
what I expected is that for the container of vector, it should invoke the clear method of the vector in the container's destruction function. for primitive types like int, it does nothing other than printing out messages.
but they give both the later one now. I wonder what's wrong here.
#include <iostream>
#include <typeinfo>
#include <vector>
using namespace std;
template <typename T> struct has_clear {
typedef char true_type;
typedef int false_type;
template <typename U, size_t (U::*)() const> struct SFINAE {
};
template <typename U> static char Test(SFINAE<U, &U::clear> *);
template <typename U> static int Test(...);
static const bool has_method = sizeof(Test<T>(nullptr) == sizeof(char));
typedef decltype(Test<T>(nullptr)) ret_type;
// typedef Test<T>(0) type_t;
};
template <typename T> class MyContainer {
// using typename has_clear<T>::true_type;
// using typename has_clear<T>::false_type;
T _obj;
public:
MyContainer(const T &obj) : _obj(obj) {}
// static void clear(MyContainer *m);
void clear(const typename has_clear<T>::true_type t)
{
cout << "the " << typeid(_obj).name() << " object has clear() function!" << endl;
cout << "typeid(t).name(): " << typeid(t).name() << endl;
_obj.clear();
cout << "clear has be done!" << endl;
}
void clear(const typename has_clear<T>::false_type t)
{
cout << "the " << typeid(_obj).name() << " object has no clear() function!" << endl;
cout << "typeid(t).name(): " << typeid(t).name() << endl;
cout << "just do nothing and quit!" << endl;
}
~MyContainer()
{
cout << "has_clear<T>::true_type: " << typeid(typename has_clear<T>::true_type()).name()
<< endl;
cout << "has_clear<T>::flase_type: " << typeid(typename has_clear<T>::false_type()).name()
<< endl;
clear(typename has_clear<T>::ret_type());
};
// template <bool b> ~MyContainer();
};
int main()
{
cout << "before MyContainer<vector<int>>" << endl;
{
vector<int> int_vec;
MyContainer<vector<int>> int_vec_container(int_vec);
}
cout << "after MyContainer<vector<int>>" << endl;
cout << "before MyContainer<int>" << endl;
{
MyContainer<int> int_container(1);
}
cout << "after MyContainer<int>" << endl;
}
it yields:
before MyContainer<vector<int>>
has_clear<T>::true_type: FcvE
has_clear<T>::flase_type: FivE
the St6vectorIiSaIiEE object has no clear() function!
typeid(t).name(): i
just do nothing and quit!
after MyContainer<vector<int>>
before MyContainer<int>
has_clear<T>::true_type: FcvE
has_clear<T>::flase_type: FivE
the i object has no clear() function!
typeid(t).name(): i
just do nothing and quit!
after MyContainer<int>
You have a bug in the implementation of has_clear:
template <typename U, size_t (U::*)() const> struct SFINAE {
}; // ^^^^^^^^^^^^^^^^^^^^^
std::vector::clear returns void and can't be const. So:
template <typename U, void (U::*)()> struct SFINAE {
};
I don't know what the issue is with your implementation has_clear, but it can be replaced with this greatly simplified, working implementation using more modern SFINAE/type_traits features:
template<typename T, typename Enable = void>
struct has_clear : std::false_type {};
template<typename T>
struct has_clear<
T,
std::enable_if_t<
std::is_same_v<decltype(&T::clear), void (T::*)()> ||
std::is_same_v<decltype(&T::clear), void (T::*)() noexcept>
>
> : std::true_type {};
And for convenience:
template<typename T>
constexpr bool has_clear_v = has_clear<T>::value;
Combined with if constexpr, you can very cleanly and simply decide which code path to run when others would fail to compile. For example:
template<typename T>
void maybe_clear(T t){
if constexpr (has_clear_v<T>){
// only compiled when T has a non-static clear() method
std::cout << "clearing " << typeid(T).name() << '\n';
t.clear();
} else {
// only compiled when T does not have a non-static clear() method
std::cout << "doing nothing with " << typeid(T).name() << '\n';
}
}
I believe this achieves what you want, but correct if I have misunderstood. This solution comes at the cost of requiring C++17.
Live Demo
I tried to implement template function specialization. You can run my tiny code in this fiddle. You can also see it below
#include <iostream>
#include <vector>
#include <list>
template <typename T>
struct is_vector {
static const bool value = false;
};
template <typename T>
struct is_vector<std::vector<T>> {
static const bool value = true;
using type = std::vector<T>;
};
template <typename T>
struct is_list {
static const bool value = false;
};
template <typename T>
struct is_list<std::list<T>> {
static const bool value = true;
using type = std::list<T>;
};
template<typename T, class = typename std::enable_if<is_list<T>::value>::type>
void foo(T t) {
std::cout << "is list" << std::endl;
}
/*
template<typename T, class = typename std::enable_if<is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
*/
//The above code will cause an error, if we uncomment it
int main()
{
foo(std::list<int>{});
return 0;
}
In this code, I have several lines commented:
template<typename T, class = typename std::enable_if<is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
If I uncomment it, I get "redifinition" error. I'm not sure how to fix it.
If I uncomment it, I get "redifinition" error. I'm not sure how to fix it.
The reason is quite simple: default values for template type arguments are not a part of a function signature. It means that you have the same template defined two times.
You might move SFINAE part in to the function return type, as it is suggested by other answers, or change the code to:
template<typename T, std::enable_if_t<is_list<T>::value, int> = 0>
void foo(T t) {
std::cout << "is list" << std::endl;
}
template<typename T, std::enable_if_t<is_vector<T>::value, int> = 1>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
You can do this instead.
template<typename T>
typename std::enable_if<is_list<T>::value>::type foo(T t) {
std::cout << "is list" << std::endl;
}
template<typename T>
typename std::enable_if<is_vector<T>::value>::type foo(T t) {
std::cout << "is vector" << std::endl;
}
Not sure if this is what you are after, but you could just check if either list or vector is a matching type:
template<typename T, class = typename std::enable_if<is_list<T>::value || is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is list" << std::endl;
}
Updated fiddle: https://godbolt.org/g/oD3o9q
Update (for C++14):
template<typename T, class = std::enable_if_t<is_list<T>::value || is_vector<T>::value>>
void foo(T t) {
std::cout << "is list" << std::endl;
}
Lets say we have some variadic template and need to treat std::reference_wrapper parameters differently.
How can we achieve that?
You can make a trait to tell if a type is reference_wrapper
template<typename T>
struct is_reference_wrapper : false_type {};
template<typename T>
struct is_reference_wrapper<reference_wrapper<T>> : true_type{};
Then you can use it to disambiguate:
template<typename T>
void do_stuff(T&& t, false_type)
{
cout << "Normal: " << t << endl;
}
template<typename T>
void do_stuff(T&& ref, true_type)
{
cout << "Ref: " << ref.get() << endl;
}
template<typename... Ts>
void foo(Ts&&... ts)
{
[[maybe_unused]] int arr[] = {
(do_stuff(forward<Ts>(ts), is_reference_wrapper<decay_t<Ts>>{}), 0)...
};
}
demo
Suppose I've written:
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
void foo() { std::cout << "T is integral." << std::endl; }
template <typename T>
void foo() { std::cout << "Any T." << std::endl; }
int main() { foo<short>(); }
When I compile this, I get an error about the ambiguity of the call (and no error if, say, I replace short with float). How should I fix this code so that I get the upper version for integral types and lower version otherwise?
Bonus points if your suggestion scales to the case of multiple specialized versions of foo() in addition to the general one.
I like Xeo's approach for this problem. Let's do some tag dispatch with a fallback. Create a chooser struct that inherits from itself all the way down:
template <int I>
struct choice : choice<I + 1> { };
template <> struct choice<10> { }; // just stop somewhere
So choice<x> is convertible to choice<y> for x < y, which means that choice<0> is the best choice. Now, you need a last case:
struct otherwise{ otherwise(...) { } };
With that machinery, we can forward our main function template with an extra argument:
template <class T> void foo() { foo_impl<T>(choice<0>{}); }
And then make your top choice integral and your worst-case option... anything:
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(choice<0> ) {
std::cout << "T is integral." << std::endl;
}
template <typename T>
void foo_impl(otherwise ) {
std::cout << "Any T." << std::endl;
}
This makes it very easy to add more options in the middle. Just add an overload for choice<1> or choice<2> or whatever. No need for disjoint conditions either. The preferential overload resolution for choice<x> takes care of that.
Even better if you additionally pass in the T as an argument, because overloading is way better than specializing:
template <class T> struct tag {};
template <class T> void foo() { foo_impl(tag<T>{}, choice<0>{}); }
And then you can go wild:
// special 1st choice for just int
void foo_impl(tag<int>, choice<0> );
// backup 1st choice for any integral
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(tag<T>, choice<0> );
// 2nd option for floats
template <class T, class = std::enable_if_t<std::is_floating_point<T>::value>>
void foo_impl(tag<T>, choice<1> );
// 3rd option for some other type trait
template <class T, class = std::enable_if_t<whatever<T>::value>>
void foo_impl(tag<T>, choice<2> );
// fallback
template <class T>
void foo_impl(tag<T>, otherwise );
One more option using tag dispatch (C++11):
#include <iostream>
void foo_impl(std::false_type) {
std::cout << "Any T." << std::endl;
}
void foo_impl(std::true_type) {
std::cout << "T is integral." << std::endl;
}
template <typename T>
void foo() {
foo_impl(std::is_integral<typename std::remove_reference<T>::type>());
//foo_impl(std::is_integral<typename std::remove_reference_t<T>>()); // C++14
}
int main() {
foo<short>(); // --> T is integral.
foo<short&>(); // --> T is integral.
foo<float>(); // --> Any T.
}
Borrowed from Scott Meyers Effective Modern C++ item 27.
One way:
template <typename T, typename std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "T is integral." << std::endl; }
template <typename T, typename std::enable_if_t<not std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "Any T." << std::endl; }
Another way is to defer to a template function object:
template<class T, typename = void>
struct foo_impl
{
void operator()() const {
std::cout << "Any T." << std::endl;
}
};
template<class T>
struct foo_impl<T, std::enable_if_t<std::is_integral<T>::value>>
{
void operator()() const {
std::cout << "T is integral." << std::endl;
}
};
template<class T>
void foo() {
return foo_impl<T>()();
}
One way to do this is:
template <typename T>
std::enable_if_t<std::is_integral<T>::value, void> foo () {
std::cout << "integral version" << std::endl;
}
template <typename T>
std::enable_if_t<!std::is_integral<T>::value, void> foo () {
std::cout << "general version" << std::endl;
}
with usage:
foo<int> ();
foo<double> ();
struct X {};
foo<X> ();
output is:
integral version
general version
general version
AFAIK, sfinae is applicable to function params so try to add parameter of dependent type with default value
template <typename T>
void foo(typename std::enable_if_t<std::is_integral<T>::value>* = 0)
{ std::cout << "T is integral." << std::endl; }
template <typename T>
void foo(typename std::enable_if_t<!std::is_integral<T>::value>* = 0)
{ std::cout << "Any T." << std::endl; }
I want to write a function print which behaves differently according to the type of its argument.
Here is my implementation:
template <typename T, typename std::enable_if<std::is_array<T>::value, int>::type = 0>
void print(const T &v) {
std::cout << "array: ";
for (const auto &e : v) {
std::cout << e << ", ";
}
std::cout << std::endl;
}
template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void print(const T &v) {
std::cout << "integral: " << v << std::endl;
}
template <typename T, typename std::enable_if<!(std::is_array<T>::value || std::is_integral<T>::value), int>::type = 0>
void print(const T &v) {
std::cout << "default: " << v << std::endl;
}
This code works as expected, but the conditions in the last specification are too complicated.
Is there any solution to simplify the last one?
A general approach you can use for a default case is to have a function which takes a variable argument list. This will only be used if no other function matches. Here is an example:
template <typename T, typename std::enable_if<std::is_array<T>::value, int>::type = 0>
void print_helper(const T &v,int) {
std::cout << "array: ";
for (const auto &e : v) {
std::cout << e << ", ";
}
std::cout << std::endl;
}
template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
void print_helper(const T &v,int) {
std::cout << "integral: " << v << std::endl;
}
template <typename T>
void print_helper(const T &v,...) {
std::cout << "default: " << v << std::endl;
}
template <typename T>
void print(const T &v)
{
print_helper(v,0);
}
For only two overloads, the extra function may not be worth it, but as you get more overloads this form can really pay off for the default case.
We can use an extra chooser to make things better for us, courtesy of Xeo:
struct otherwise{ otherwise(...){} };
template<unsigned I>
struct choice : choice<I+1>{};
// terminate recursive inheritence at a convenient point,
// large enough to cover all cases
template<> struct choice<10>{};
Then each ranking on our choice list will be preferred to the next, and we just have to disable as we go:
// just forward to our first choice
template <class T> void print(const T &v) { print(v, choice<0>{}); }
Where our top choice is array:
template <class T, class = std::enable_if_t<std::is_array<T>::value>>
void print(const T& v, choice<0> ) { ... }
And then integral:
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void print(const T& v, choice<1> ) { ... }
And then anything
template <class T>
void print(const T& v, otherwise ) { ... }
This structure allows for arbitrarily many choices.