Antoine
Antoine

Reputation: 14064

Is SFINAE forbidden in template arguments, or did I hit a clang bug?

What follows is a simplified version of a problem I hit with real code.

Short version: just look at the code and error at gcc.godbolt.org / long version: read on ;)

Suppose I want a class with a template parameter setting and a method int func(int) such as:

The simplest way to do that is to specialize the class template:

template<bool setting> struct A {
    int func(x) const { return 2 * x; }
};
template<> struct A<false> {
    int func(x) { return x; }
};

The problem with this approach is that if I have a bunch of other methods that don't depend on setting, I'll have to copy-paste them in both specialization (or inherit from a common base, when there's not too much inter-dependencies).

So instead, I can use SFINAE to select the right method, e.g. with std::enable_if. This requires the method to have a template argument, because substitution failure must invalidate just the method, not the whole class. As far as I know, the failure can occur in either of:

Here's the code using the method's arguments:

template<bool setting> struct B {
    template<bool when=true>
    int func(int x
            , typename std::enable_if<when && setting>::type * u=0
            )
    { return 2 * x; }

    template<bool when=true>
    int func(int x
            , typename std::enable_if<when && !setting>::type * u=0
            )
    { return x; }
};

And here's the version using the method's template arguments:

template<bool setting> struct C {
    template<bool when=true, typename std::enable_if<
              when && setting
            >::type...>
    int func(int x) { return 2 * x; }

    template<bool when=true, typename std::enable_if<
              when && !setting
            >::type...>
    int func(int x) { return x; }
};

I tend to prefer the last version, as it makes the method's signature more readable, but that's a matter of personal taste.

My question concerns this last version: is it valid C++ ? gcc compiles it fine, but clang does not (tested with -std=c++11 / c++1y / c++1z with same results). The class definition in itself compiles OK, but the error occurs when it's instantiated:

int main() {
    A<true> a;
    B<true> b;
    C<true> c;
    return a.func(1) + b.func(2) + c.func(3);
}

compiles in gcc 5.3 but not with clang 3.7.1:

test.cpp:30:36: error: call to member function 'func' is ambiguous
                return a.func(1) + b.func(2) + c.func(3);
                                            ~~^~~~
test.cpp:20:7: note: candidate function [with when = true, $1 = <>]
                int func(int x) { return 2 * x; }
                    ^
test.cpp:23:7: note: candidate function [with when = true, $1 = <>]
                int func(int x) { return x; }
                    ^
1 error generated.

So is this valid C++ ? Is it a clang bug or is gcc wrong in accepting this code ?

Upvotes: 4

Views: 250

Answers (1)

Jarod42
Jarod42

Reputation: 217235

Is SFINAE forbidden in template arguments

It is valid. You may do for example:

template<bool setting> struct C {
    template<bool when=true, typename std::enable_if<
              when && setting
            >::type* = nullptr>
    int func(int x) { return 2 * x; }

    template<bool when=true, typename std::enable_if<
              when && !setting
            >::type* = nullptr>
    int func(int x) { return x; }
};

Demo

The problem with typename std::enable_if<when && !setting>::type... should be related to CWG 1558.
And so your code should be correct in C++17.

Upvotes: 3

Related Questions