I've got a problem with a C++ meta-function that I don't understand. I'm compiling on Apple's build of clang 8.1.0, using C++14. Working code that illustrates the problem is below.
I've cribbed a metafunction from elsewhere and I'm trying to use it. It is intended to detect functions named 'bananify' that have a parameter of the type passed to the metafunction. You call it as...
BananifyDetector<int>::value
is should return true if it can see a declared function of the form ...
bananify(int)
The problem is that it only works if the function being searched for is declared before the template definition of BananifyFinder, as opposed to the instantiation of it. So in my example code I would have expected both,
BananifyFinder<int>
BananifyFinder<std::string>
to have succeeded with the code I have below, but because of where bananify(std::string) was defined it fails.
This is frustrating as if I put function detectors in header files I have to be include order aware in client code, which is a profound pain and possibly impossible to get right in some circumstances.
I'm not sure what is going on here. Is it a C++ feature, a bug in clang or something dumb I've done?
Any help appreciated.
#include <iostream>
#include <type_traits>
////////////////////////////////////////////////////////////////////////////////
// A bananify function to be detected
// This is successfully found.
double bananify(int)
{
return 0.0;
}
/// A meta function that detects if a single argument function named 'bananify'
/// exists with takes an argument of the type passed to the metafunction.
///
/// Note, automatic casts will get in the way occasionally so if function
/// bananify(float) exists, a BananifyFinder<int>::value will return true.
template<class ARG1>
class BananifyFinder {
private :
template<typename ...> using VoidT_ = void;
template<typename A1, typename = void>
struct Test_ : std::false_type
{
typedef void returnType;
};
template<typename A1>
struct Test_<A1, VoidT_<decltype(bananify(std::declval<A1>()))>> : std::true_type
{
typedef decltype(bananify(std::declval<A1>())) returnType;
};
public :
typedef typename Test_<ARG1>::returnType returnType;
constexpr static bool value = Test_<ARG1>::value;
};
////////////////////////////////////////////////////////////////////////////////
// A bananify function to be detected that takes std::strings
// This fails to be found, but if we move it before the declaration of BananifyFinder it
// will be found;
std::string bananify(std::string)
{
return "s";
}
// dummy class with no bananify function to be found
class Nothing{};
// check the results of the metafunction 'T'
template<class T>
void CheckBanana(const std::string &str)
{
using DetectedType = BananifyFinder<T>;
std::cout << str << " detected is " << DetectedType::value << std::endl;
std::cout << str << " returns is " << typeid(typename DetectedType::returnType).name() << std::endl << std::endl;
}
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
{
// this should print "BananifyFinder<int> 1 d"
CheckBanana<int>("BananifyFinder<int> ");
// this should print "BananifyFinder<std::string> 1 NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE"
// but it prints "BananifyFinder<std::string> 0 v"
// FAILS
CheckBanana<std::string>("BananifyFinder<std::string> ");
// this should print "BananifyFinder<Nothing> 0 v"
CheckBanana<Nothing>("BananifyFinder<Nothing> ");
}