Reputation: 1234
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:
GCC 8.2 -std=c++17
- FAIL to compileGCC trunk -std=c++17
- OKMSVC 19.14 /std:c++17 /permissive-
- OKMSVC 19.16 /std:c++17 /permissive-
- OKclang 7 -std=c++17
- FAIL to compileclang trunk -std=c++17
- FAIL to compileSo, the questions:
Args...
of the Outer
class in the immediate context of the Inner
class? Upvotes: 2
Views: 86
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
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