Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default template specialization with multiple conditions

Tags:

c++

c++11

sfinae

I'd like to define functions for integral types, string and other types.

I can write:

template<typename T, typename = std::enable_if<std::is_integral<T>::value>::type>
void foo();

template<typename T, typename = std::enable_if<std::is_same<std::string>::value>::type>
void foo();

But how I can define function that will be called in other cases (if T not integral type and not std::string)?

like image 504
renzo Avatar asked Aug 04 '17 08:08

renzo


1 Answers

I'm pretty sure that writing something like the line below becomes quite annoying and error-prone when you want to write up to N sfinae'd versions of foo:

std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type

To avoid it, you can use the choice trick (a simple way to exploit overload resolution actually).
It follows a minimal, working example:

#include <iostream>
#include <utility>
#include <string>

template<int N> struct Choice: Choice<N-1> {};
template<> struct Choice<0> {};

template<typename T, typename... Args>
std::enable_if_t<std::is_integral<T>::value>
bar(Choice<2>, Args&&...) { std::cout << "integral" << std::endl; }

template<typename T, typename... Args>
std::enable_if_t<std::is_same<T, std::string>::value>
bar(Choice<1>, Args&&...) { std::cout << "string" << std::endl; }

template<typename T, typename... Args>
void bar(Choice<0>, Args&&...) { std::cout << "whatever" << std::endl; }

template<typename T, typename... Args>
void foo(Args&&... args) { bar<T>(Choice<100>{}, std::forward<Args>(args)...); }

int main() {
    foo<bool>("foo");
    foo<std::string>(42);
    foo<void>(.0, "bar");
}

It handles nicely also the arguments that are directly forwarded to the right function once it has been picked up from the set.

The basic idea is that it tries to use all the versions of your function in the order you specified, from N to 0. This has also the advantage that you can set a priority level to a function when two of them match the template parameter T with their sfinae expressions.
The sfinae expressions enable or disable the i-th choice and you can easily define a fallback by using the Choice<0> tag (that is far easier to write than std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type).
The drawback (even though I wouldn't consider it a drawback) is that it requires an extra function that simply forwards the arguments to the chain by appending them to the Choice<N> tag.

See it up and running on Coliru.

like image 191
skypjack Avatar answered Sep 28 '22 09:09

skypjack