Zark Bardoo
Zark Bardoo

Reputation: 518

Separate definition and declaration of template member function using enable_if whose template parameter also includes a constexpr member function

The following doesn't compile under g++ 8.1.0 on CentOS 7:

hey.h

#pragma once
#include <iostream>
#include <type_traits>

class Valid {};
class Invalid {};

struct Hey
{
  template<typename T>
  static constexpr bool is_valid() { return std::is_same_v<T, Valid>; }

  template<typename T, std::enable_if_t<is_valid<T>()>* = nullptr>
    void howdy() const;
};

template<typename T, std::enable_if_t<Hey::is_valid<T>()>*>
  void Hey::howdy() const
{
  std::cout << "Howdy" << std::endl;
}

Compiler output:

In file included from hey.cpp:1:
hey.h:18:8: error: no declaration matches ‘void Hey::howdy() const’
   void Hey::howdy() const
        ^~~
hey.h:14:10: note: candidate is: ‘template<class T, std::enable_if_t<is_valid<T>()>* <anonymous> > void Hey::howdy() const’
     void howdy() const;
          ^~~~~
hey.h:8:8: note: ‘struct Hey’ defined here
 struct Hey
        ^~~

Amazingly, all I have to do to both compile correctly and get the desired behavior is add a typedef in Hey:

hey.h (fixed, first few boring lines skipped)

struct Hey
{
  template<typename T>
  static constexpr bool is_valid() { return std::is_same_v<T, Valid>; }

  template<typename T>
  using EnableType = std::enable_if_t<is_valid<T>()>;

  template<typename T, EnableType<T>* = nullptr>
    void howdy() const;
};

template<typename T, Hey::EnableType<T>*>
  void Hey::howdy() const
{
  std::cout << "Howdy" << std::endl;
}

hey.cpp

#include "hey.h"

int main(int, char**)
{
  Hey hey;
  hey.howdy<Valid>();

  // Adding this line breaks the build, as it should:
  // hey.howdy<Invalid>();

  return 0;
}

After many tweaks, I've narrowed the compiler error situation to the fact that 1) is_valid() is a member of Hey and 2) howdy() is declared inside Hey's body but defined outside. If you delete the using and make is_valid() a standalone function outside of Hey, there are no issues compiling. If you delete the using and define howdy() inside the class definition, there are also no issue compiling. But when howdy() is defined outside of the class definition, is_valid() is declared inside of the class definition, and the using isn't present, the compiler fails. Is this correct behavior? Am I looking at a compiler bug?

Upvotes: 3

Views: 1559

Answers (1)

T.C.
T.C.

Reputation: 137315

The matching of expressions in template declarations is based on equivalence, a concept based on the one-definition rule. For two expressions to be considered equivalent, they must be at least token-by-token identical modulo the renaming of template parameters.

The expressions is_valid<T>() and Hey::is_valid<T>() are not equivalent (the second has two tokens the first doesn't have), and so the compiler is not required to match them.

Hey::EnableType<T> is a type, and is not subject to the strict equivalence rules for expressions.

Upvotes: 5

Related Questions