Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to constrain a template? Six different usages of std::enable_if

I'm trying to understand two different versions of a template function that uses std::enable_if<>.

Version 1:

template<class T, typename std::enable_if<std::is_convertible<T, std::string_view>::value, T>::type* = nullptr>
void foo(const T& msg);

Version 2:

template<class T, typename = typename std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);

If I understood it correctly, if the condition is met they should be converted into:

// Version 1
template<class T, T* = nullptr>
void foo(const T& msg);

// Version 2
template<class T, typename = void>
void foo(const T& msg);

Both versions can be equally called by:

std::string s = "Test";
foo(s);

What is the difference between those two versions? When should one be used?


Second question

Because of an error on my part, I discovered that version 2 also compiles, if one typename is missing:

//Correct Version 2 like above:
template<class T, typename = typename std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);

// My "faulty" version, also works. Is this correct too?
template<class T, typename = std::enable_if<std::is_convertible<T, std::string_view>::value>::type>
void foo(const T& msg);

Is the second (faulty) version also correct? I thought std::enable_if<> does need a typename in front of it.

like image 690
Marc Avatar asked Sep 03 '25 16:09

Marc


1 Answers

How should one constrain a template?

If you are not limited to compatibility with older C++ standards (pre C++20), and you don't need to refer to the template type, and the constraints only involve a single template parameter, prefer the least boilerplate option:

// #1
void foo(const std::convertible_to<std::string_view> auto& msg);

Otherwise, prefer the slightly more verbose form:

// #2
template <typename T>
    requires std::convertible_to<T, std::string_view>
void foo(const T& msg);

The form #2 gives a name to the template type and continues to function if the constraints involve multiple template parameters. It is still not directly applicable to older C++, but the location of the constraint is compatible with older C++ enable_if usage:

// #2, compatible version

// C++11
#define TEMPLATE(...)            template <__VA_ARGS__
#define REQUIRES(C)              , typename std::enable_if<(C), int>::type = 0>
#define CONVERTIBLE_TO(From, To) std::is_convertible<From, To>::value

// C++20
#define TEMPLATE(...)            template <__VA_ARGS__>
#define REQUIRES(C)              requires (C)
#define CONVERTIBLE_TO(From, To) std::convertible_to<From, To>

TEMPLATE(typename T)
    REQUIRES(CONVERTIBLE_TO(T, std::string_view))
void foo(const T& msg);

The following options are also available, but I would stick to #1 or #2:

// #3
template <std::convertible_to<std::string_view> T>
void foo(const T& msg);

// #4
template <typename T>
void foo(const T& msg) requires std::convertible_to<T, std::string_view>;

With respect to enable_if, there are three options:

// #5, non-type template parameter with default value ("version 1")
template <typename T, typename std::enable_if_t<std::is_convertible_v<T, std::string_view>, int> = 0>
void foo(const T& msg);

// #6, enable_if in the return type
template<typename T>
auto foo(const T& msg) -> typename std::enable_if_t<std::is_convertible_v<T, std::string_view>>;

// #7, defaulted template parameter ("version 2")
template<class T, typename = typename std::enable_if_t<std::is_convertible_v<T, std::string_view>>>
void foo(const T& msg);

Option #7 ("version 2") is rarely advisable, because default template parameters do not participate in the function signature. So, once you have two overloads, it is ambiguous. And overload sets grow.

Option #6 is not available for constructors, which lack a return type. But, in #6, you can name the function parameters which can be handy.

Option #5 is the most general SFINAE option. Prefer it, if you must SFINAE.

Regarding question #2, the relaxation on typename came in C++20, and is described here and here

like image 67
Jeff Garrett Avatar answered Sep 05 '25 06:09

Jeff Garrett



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!