I found a strange behavior with a custom type trait I was writing and further found that different compilers are having different behavior with the trait. The goal is simple, I want to detect if a given function MyFunction
is defined/overloaded for a specific input type. I made a trait HasMyFunction
to do this. The example code and godbolt link for 3 compilers behavior is below.
To me the clang and gcc behavior is surprising/incorrect. The trait returns false for primitive types and even std::string
unless MyFunction
for those types is defined PRIOR to the template definition for the trait. However if I use a locally defined/custom type the same trait always returns true, no matter if the class and its MyFunction
is defined before or after the trait.
MSVC returns true in all cases regardless of the ordering.
My understanding is that templates don't "exist" until they are instantiated and given that the instantiation in all cases is well after all these other declarations I don't see why this ordering would matter.
What is the correct behavior here, and which of the compilers is non compliant? This does not seem like the type of thing that would be compiler dependent or left to the implementers....
Godbolt
#include <iostream>
#include <type_traits>
#include <string>
/// Helper to determine if a type has a specialization of the MyFunction function
template <typename T, typename = void>
struct HasMyFunction : public std::false_type{};
template <typename T>
struct HasMyFunction<T, std::void_t<decltype(MyFunction(std::declval<T>()))>> : public std::true_type{};
//Trait works no matter the location of this def
struct Test{};
std::string MyFunction(Test)
{
return "";
}
struct TestBad{};
//Move me up above line 5 and the trait works as you would expect (true)
//MSVC seems to work in all cases
//gcc/clang gives false unless this is declared before the template....
std::string MyFunction(int)
{
return "";
}
//Same behavior with std::string (non primative type....)
std::string MyFunction(std::string)
{
return "";
}
int main() {
std::cout << HasMyFunction<int>::value << std::endl;
std::cout << HasMyFunction<std::string>::value << std::endl;
std::cout << HasMyFunction<Test>::value << std::endl;
std::cout << HasMyFunction<TestBad>::value << std::endl;
}
I found a strange behavior with a custom type trait I was writing and further found that different compilers are having different behavior with the trait. The goal is simple, I want to detect if a given function MyFunction
is defined/overloaded for a specific input type. I made a trait HasMyFunction
to do this. The example code and godbolt link for 3 compilers behavior is below.
To me the clang and gcc behavior is surprising/incorrect. The trait returns false for primitive types and even std::string
unless MyFunction
for those types is defined PRIOR to the template definition for the trait. However if I use a locally defined/custom type the same trait always returns true, no matter if the class and its MyFunction
is defined before or after the trait.
MSVC returns true in all cases regardless of the ordering.
My understanding is that templates don't "exist" until they are instantiated and given that the instantiation in all cases is well after all these other declarations I don't see why this ordering would matter.
What is the correct behavior here, and which of the compilers is non compliant? This does not seem like the type of thing that would be compiler dependent or left to the implementers....
Godbolt
#include <iostream>
#include <type_traits>
#include <string>
/// Helper to determine if a type has a specialization of the MyFunction function
template <typename T, typename = void>
struct HasMyFunction : public std::false_type{};
template <typename T>
struct HasMyFunction<T, std::void_t<decltype(MyFunction(std::declval<T>()))>> : public std::true_type{};
//Trait works no matter the location of this def
struct Test{};
std::string MyFunction(Test)
{
return "";
}
struct TestBad{};
//Move me up above line 5 and the trait works as you would expect (true)
//MSVC seems to work in all cases
//gcc/clang gives false unless this is declared before the template....
std::string MyFunction(int)
{
return "";
}
//Same behavior with std::string (non primative type....)
std::string MyFunction(std::string)
{
return "";
}
int main() {
std::cout << HasMyFunction<int>::value << std::endl;
std::cout << HasMyFunction<std::string>::value << std::endl;
std::cout << HasMyFunction<Test>::value << std::endl;
std::cout << HasMyFunction<TestBad>::value << std::endl;
}
Share
Improve this question
edited Jan 17 at 19:38
I Less3 CPP
asked Jan 17 at 19:33
I Less3 CPPI Less3 CPP
1215 bronze badges
5
|
1 Answer
Reset to default 2@kjpus got me on the path to finding this with the specific mention of dependent name, but he didn't submit in the form of an answer so I am putting the answer here explicitly.
So this is due to dependent name resolution and is explained quite clearly on cppref with an example well aligned with my original example code.
Non-dependent names are looked up and bound at the point of template definition. This binding holds even if at the point of template instantiation there is a better match:
#include <iostream>
void g(double) { std::cout << "g(double)\n"; }
template<class T>
struct S
{
void f() const
{
g(1); // "g" is a non-dependent name, bound now
}
};
void g(int) { std::cout << "g(int)\n"; }
int main()
{
g(1); // calls g(int)
S<int> s;
s.f(); // calls g(double)
}
/permissive-
compiler option, but reverts to what you observed if you also add/Zc:twoPhase-
. This means the lack of 2-phase lookup in MSVC by default is the reason why it differs from the other compilers. But I do not know why this behavior is correct. – Eugene Commented Jan 17 at 21:45