3

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> ");
}
brunobignose
  • 106
  • 1
  • 6

3 Answers3

1

Template are parsed in two phases.

In the first one, expression that are independent from the template arguments are resolved. In the second one, template dependent argument dependents are resolved where Argument dependent lookup is performed.

decltype(bananify(std::declval<A1>()))> is argument dependent construction (depends on A1).

From this page

And ADL examines function declarations with external linkage that are visible from both the template definition context and the template instantiation context (in other words, adding a new function declaration after template definition does not make it visible, except via ADL).

Thus, your code look into std:: (with ADL) and does not find a bananify function.

Moving it before the template instantiation is enough to qualify it for lookup.

Davidbrcz
  • 2,335
  • 18
  • 27
1

I believe that the bananify reference is resolved in the template before instantiation, since it's not dependent. Hence the non-declared overrides are not seen.

Typically you want to search for functions being available as members of a type, rather than at the top-level, in which case the problem goes away:

#include <iostream>
#include <type_traits>
#include <typeinfo>

class A {
  public:
  double bananify(int)
  {
    return 0.0;
  }
};

// Find bananify(ARG1) as a member of C:
template<class C, 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(std::declval<C>().bananify(std::declval<A1>()))>> : std::true_type
  {
    typedef decltype(std::declval<C>().bananify(std::declval<A1>())) returnType;
  };

public :
  typedef typename Test_<ARG1>::returnType returnType;
  constexpr static bool value = Test_<ARG1>::value;
};

class B {
  public:
  std::string bananify(std::string)
  {
    return "s";
  }
};


// check the results of the metafunction 'T'
template<class C, class T>
void CheckBanana(const std::string &str)
{
  using DetectedType = BananifyFinder<C,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[])
{
  CheckBanana<A,int>("BananifyFinder<int> "); // ok
  CheckBanana<B,std::string>("BananifyFinder<std::string> "); // ok
}
davmac
  • 20,150
  • 1
  • 40
  • 68
0

Someone else left an answer but it seems to have been deleted. It's a name dependent lookup issue with templates.

Addressed here Name lookups in C++ templates

and more detail here http://en.cppreference.com/w/cpp/language/adl

brunobignose
  • 106
  • 1
  • 6