Is the "lazy man's enable_if" legal C++?
C++C++11Visual C++Language LawyerSfinaeC++ Problem Overview
I frequently use a technique I call the "lazy man's enable_if
," where I use decltype
and the comma operator to enable a function based on some template input. Here is a small example:
template <typename F>
auto foo(F&& f) -> decltype(f(0), void())
{
std::cout << "1" << std::endl;
}
template <typename F>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
std::cout << "2" << std::endl;
}
With --std=c++11
, g++ 4.7+ and Clang 3.5+ happily compile that bit of code (and it works as I would expect). However, when using MSVC 14 CTP5, I get this error complaining of foo
already being defined:
> Error error C2995: 'unknown-type foo(F &&)': function template has > already been defined c++-scratch main.cpp 15
So my question is: Is the "lazy man's enable_if
" legal C++ or is this an MSVC bug?
C++ Solutions
Solution 1 - C++
[temp.over.link]/6 specifies when two function template declarations are overloads. That is done by defining equivalency of two function templates as follows:
> Two function templates are equivalent if they [..] have return types [..] that are equivalent using the rules > described above to compare expressions involving template parameters.
The "rules described above" are
> Two expressions involving template parameters are considered > equivalent if two function definitions containing the expressions would satisfy the one definition rule (3.2) [..]
The ODR relevant for this part states in [basic.def.odr]/6 that
> Given such an entity named D
defined in more than one translation
> unit, then
>
> - each definition of D
shall consist of the same sequence of tokens;
Clearly, as the return types (which are the trailing return types as per [dcl.fct]/2) do not consist of the same tokens, two function definitions containing those expressions would violate the ODR.
Hence the declarations of foo
declare non-equivalent function templates and overload the name.
The error you see is issued due to the lack of support from VC++ for expression SFINAE - presumably the trailing-return-types are not inspected for equivalency.
Workaround
You can make the function templates non-equivalent in another way - Change the template parameter list. If you rewrite the second definition like so:
template <typename F, int=0>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
std::cout << "2" << std::endl;
}
Then VC++ compiles it fine. I shortened the quote in [temp.over.link]/6, which covers this:
> Two function templates are equivalent if they are declared in the same > scope, have the same name, have identical template parameter lists [..]
In fact, to be able to easily introduce new overloads, you can use a little helper:
template <int I>
using overload = std::integral_constant<int, I>*;
Usage is e.g.
// Remember to separate > and = with whitespace
template <typename... F, overload<0> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1)..., void())
template <typename... F, overload<1> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1, 2)..., void())
Demo.
Solution 2 - C++
This is a feature called "Expression SFINAE." It is not yet fully supported by Visual C++ (see "C++11/14/17 Features In VS 2015 Preview" for the latest conformance update as of the time of this answer).