sthlm58
sthlm58

Reputation: 1234

Different behavior across compilers for std::enable_if (dependent on Outer class template parameters)

I have a nested (Inner) class, for which I want to enable_if a constructor, depending on how many template parameters (Args) the enclosing class (Outer) has.

I came up with the code below, only to find out that it compiles fine on some compilers, and on some not.

#include <tuple>
#include <type_traits>


template <typename... Args>
struct Outer {

    struct Inner {
        Inner(const Outer* out, Args... vals) 
            : outer(out)
            , values(vals...)
         {}

         // This ctor should be enabled only if Args are non-empty
         template <typename = std::enable_if_t<(sizeof...(Args) > 0)>>
         Inner(const Outer* out)
            : outer(out)
         {}

        const Outer* outer;
        std::tuple<Args...> values;
    };

};

int main()
{
    Outer<int, int> o1;
    Outer<int, int>::Inner i1(&o1, 1, 2);
    Outer<int, int>::Inner i11(&o1);

    Outer<> o2;
    Outer<>::Inner i2(&o2);
    Outer<>::Inner i21(nullptr);

}

Shown on Godbolt: https://godbolt.org/z/lsivO9

The funny part are the results:


So, the questions:

Upvotes: 2

Views: 86

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275500

template <typename = std::enable_if_t<(sizeof...(Args) > 0)>>
     Inner(const Outer* out)

When Args is empty, this is

template <typename = std::enable_if_t<false>>
     Inner(const Outer* out)

SFINAE only applies to functions when the SFINAE is dependent on the template parameters of the function. Here it is not. So, a hard error is proper.

This may be a case where no diagnostic is required, and your program is still ill-formed (so the compiler is free to do anything at all). Teasing that out is tricky, and as there is an easy workaround you might as well do it.

template <std::size_t N = sizeof...(Args), typename = std::enable_if_t<(N > 0)>>

or my preferred

template <std::size_t N = sizeof...(Args), std::enable_if_t<(N > 0), bool> = true>

which works better when there are overloads.

However, in your specific case, the first ctor becomes the second one:

    Inner(const Outer* out, Args... vals) 
        : outer(out)
        , values(vals...)
     {}

when Args... is empty, so I don't see the point here.

Upvotes: 1

Jans
Jans

Reputation: 11250

You need to make std::enable_if dependent on the constructor template parameter

template <std::size_t N = sizeof...(Args), std::enable_if_t<(N > 0), bool> = true>
Inner(const Outer* out)
: outer(out)
{}

Upvotes: 3

Related Questions