Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"anti-SFINAE" enabling an overload if a given expression is *not* well-formed

Tags:

c++

c++11

sfinae

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.


A proposed solution

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);
}

--

My (first) question

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?

--

Why can't I use an alias instead?

(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>
like image 803
Aaron McDaid Avatar asked Aug 14 '15 16:08

Aaron McDaid


1 Answers

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.

like image 151
Ville Voutilainen Avatar answered Oct 13 '22 00:10

Ville Voutilainen