It is easy to use SFINAE to hide a particular function overload if a particular expression is not well-formed. But I want to do the opposite, hiding an overload if and only if a given expression is well-formed, and to do so in a very general way. I have a solution that's working in clang 3.5.0 and gcc 5.2.0, but I'm interested in any comments and alternatives.
Ideally, there would be a builtin constexpr bool
function/macro that would tell us at compile time whether a particular expression is well formed.
IS_WELL_FORMED( declval<T>() < declval<T>() ) // I want this as bool
which could be used with enable_if
to enable or disable overloads.
I have found a solution, but I have come across some strange behaviour in g++ 5.2.0 and clang 3.5.0 and I wonder if either are buggy.
First, the most robust solution I've found so far, which works on both compilers. For example, I want to test if T
has a .length()
method. This requires "hiding" the expression inside another template. Also, a function called well_formed_default
that I'll discuss later.
// Define a template to contain our expression
template<typename T2=T, typename =
decltype( declval<T2>().length() ) // This line is the expression to test
> struct test_for_length_method { };
and here is how it might be used in a containing class:
template<typename T>
struct Demo { // the main struct I'm working on
// Define a template to "hide" our expression
template<typename T2=T, typename =
decltype( declval<T2>().length() ) // This line is the expression to test
> struct test_for_length_method { };
// test if the above "test" succeeds
constexpr bool T_has_length =
well_formed_default< test_for_length_method >();
// demonstrate the bool in an enable_if
template<bool b = T_has_length>
static
auto demo_enable_if() -> typename std::enable_if< b >::type {
cout << "T has length" << endl;
}
template<bool b = T_has_length>
static
auto demo_enable_if() -> typename std::enable_if< !b >::type {
cout << "T doesn't" << endl;
}
}
The above works as expected with Demo<int>::demo_enable_if()
and Demo<std::string>::demo_enable_if()
.
I can't use T_has_length
directly inside the enable_if
as it will lead to hard errors because it's not template substitution. Therefore, I pretend it is a template parameter by making a copy of if in another template parameter bool b = T_has_length
. This is similar to how we have to use typename T2=T
inside the test struct. A bit annoying, but I guess it makes sense.
I now define well_formed_default
. It takes a template (and optionally some types) and returns true or false depending on whether it can construct the template with those particular args. I would include it automatically in all my projects. Perhaps something like this already exists in the standard?
template<template<class...> class Template, class ...Args>
constexpr auto well_formed_default_impl(int)
-> typename std::conditional<false,Template<Args...>, bool>::type {
return true;
}
template<template<class...> class Template, class ...Args>
constexpr auto well_formed_default_impl(...)
-> bool {
return false;
}
template<template<class...> class Template, class ...Args>
constexpr bool well_formed_default() {
return well_formed_default_impl<Template,Args...>(0);
}
--
This works in both g++ 5.2.0 and clang 3.5.0. But should it? Is this fully standard, or am I pushing the standard too far? I guess the weirdest thing for me is the use of Template<Args...>
inside well_formed_default_impl
- is this guaranteed to be a substitution failure in the way I use it? e.g. with test_for_pushback_method_struct<>
when the relevant decltype
isn't well-formed?
--
(For the remainder of this question, it might help to look at the output of this code on Coliru as it has all the tests and results I'm discussing below.)
I began this project with an alias instead of the above struct. I thought it would be equivalent. But instead, with both compilers, they think that string
does not have a length method.
template<typename T2=T>
using test_for_length_method_alias = decltype( declval<T2>().length() );
Finally, I tried both the struct and the alias, but where I explicitly define the first type parameter (T
) instead of relying on the the default T2=T
. This shouldn't change anything, because I'm passing the type that is the default - but it does change the behaviour! Using the struct with an explicit first parameter
well_formed_default<test_for_length_method_struct , T>
works correctly on both compilers. But the alias-with-explicit-first-type only works correctly with clang:
well_formed_default<test_for_length_method_alias , T>
It's somewhat unlikely that there will be any changes to the language to support this sort of uses directly (except what concepts will bring). Concepts will make it easier to write mutually exclusive overloads and negated concepts allow writing overloads that are enabled if an expression is not well-formed. (And yes, I know that that doesn't help you write such code for C++11 or C++14.) Example:
#include <iostream>
template <class T>
requires !requires(T t) {t.f();}
void f()
{
std::cout << "does not support f()!" << std::endl;
}
template <class T>
void f()
{
std::cout << "supports f()!" << std::endl;
}
struct A {};
struct B {void f() {}};
int main()
{
f<A>();
f<B>();
}
I added the unconstrained overload just for illustration purposes. Leaving it out would make the call f<B>() ill-formed. The code works today on gcc trunk, and you can try it out on melpon.org/wandbox.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With