Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this considered valid c++11 or c++14? Or is gcc/clang getting it wrong?

While trying to solve Is it possible to tell if a class has hidden a base function in C++?, I generated this:

#include <type_traits>
#include <iostream>

#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0

template<class T, class B, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f(T*) -> std::true_type;

template<class T, class B>
auto has_x_f(B*) -> std::false_type;

template<class T, class B>
using has_x = decltype(has_x_f<T, B>((T*)nullptr));

template<typename T>
struct A
{
  void x() {}

  static const bool x_hidden;

  template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
  void y(R value)
  {
     std::cout << "x() is hidden" << std::endl;
  }

  template <typename R, ENABLE_IF(std::is_same<T, R>::value && !x_hidden)>
  void y(R value)
  {
     std::cout << "x() is not hidden" << std::endl;
  }

  //using t = std::integral_constant<bool, x_hidden>;
};

struct B : A<B>
{
    void x() {}
};

struct C : A<C>
{
};

template<typename T>
const bool A<T>::x_hidden = has_x<T, A<T>>::value;

int main()
{
  B b;
  C c;

  std::cout << "B: ";
  std::cout << b.x_hidden << std::endl;
  std::cout << "C: ";
  std::cout << c.x_hidden << std::endl;

  std::cout << "B: ";
  b.y(b);
  std::cout << "C: ";
  c.y(c);

  return 0;
}

Which outputs what I want:

B: 1
C: 0
B: x() is hidden
C: x() is not hidden

clang and gcc both compile and execute this "correctly", but vc++ doesn't (though I am aware that there are problems with it working properly with expressions similar to template <typename T> ... decltype(fn(std::declval<T>().mfn()))).

So my question is, is this considered valid or will it break later on? I'm also curious about the x_hidden being able to be used as a template parameter in the functions but not being able to use it in using t = std::integral_constant<bool, x_hidden>. Is that just because the template's type isn't fully declared at this point? If so, why did using it work for the function declarations?

like image 610
Adrian Avatar asked May 03 '17 00:05

Adrian


People also ask

What version of C does Clang use?

If you are looking for source analysis or source-to-source transformation tools, Clang is probably a great solution for you. Clang supports C++11, C++14 and C++17, please see the C++ status page for more information.

Does Clang support C++11?

You can use Clang in C++11 mode with the -std=c++11 option. Clang's C++11 mode can be used with libc++ or with gcc's libstdc++.

Is Clang GCC compatible?

GCC and C99 allow an array's size to be determined at run time. This extension is not permitted in standard C++. However, Clang supports such variable length arrays for compatibility with GNU C and C99 programs. If you would prefer not to use this extension, you can disable it with -Werror=vla.

Does Google use GCC or Clang?

Since a few months back Google switched from GCC to Clang for compiling their production builds of the Chrome web-browser on Linux. A Google developer has now shed some light on the switch with backing up their own reasons for switching to Clang.


2 Answers

If x_hidden is false, there is no template arguements for which this template function

template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value) {
  std::cout << "x() is hidden" << std::endl;
}

can be instantiated, so your program is ill formed no diagnostic required. This is a common hack, its illegality may be made clear or even legal at some point.

There may be a reason for using has_x_f instead of just directly initializing is_hidden with the is_same clause, but it isn't demonstrated in your code.

For any template specialization, there must be arguments which would make the instantiation valid. If there are not, the program is ill-formed no diagnostic required.

I believe this clause is in the standard to permit compilers to do more advanced checks on templates, but not require them.

template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
   std::cout << "x() is hidden" << std::endl;
}

the compiler is free to notice x_hidden is false, and say "it doesn't matter what is_same<T,R> is", and deduce that no template arguments could make this specialization valid. Then generate an error.

An easy hack is

template <class T2=T, class R,
  ENABLE_IF(std::is_same<T2, R>::value && has_x<T2, A<T2>>::value)
>
void y(R value)
{
   std::cout << "x() is hidden" << std::endl;
}

where we sneak another template argument in that equals T usually. Now, the compiler has to admit the possibility that T2 passes the has_x test, and that the passed argument is R. Users can bypass this by manually passing the "wrong" T2.

This may not solve everything. The standard is a bit tricky to read here, but one reading states that if within the body of y() we go and assume that our T itself has x(), we still violate the rule of the possibility of a valid template instantiation.

[temp.res] 14.6/8 (root and 1)

Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:

  • no valid specialization can be generated for a template [...] and the template is not instantiated, or

No valid specialization for

template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
  std::cout << "x() is hidden" << std::endl;
}

can be generated if x_hidden is false. The exitence of another overload is immaterial.

If you fix it using the T2 trick, the same rule holds if the body assumes T=T2.

Three are words in the standard that attempt to not cause the template to be instantiated in certain contexts, but I am unsure if that makes the above code well formed or not.

like image 145
Yakk - Adam Nevraumont Avatar answered Nov 01 '22 01:11

Yakk - Adam Nevraumont


I tried compiling your code with the Intel C++ compiler(icpc (ICC) 17.0.2 20170213), and it would not compile with the following message:

main.cpp(30): error: expression must have a constant value
    template <typename R, ENABLE_IF(std::is_same<T, R>::value && !x_hidden)>
                          ^

/home/com/gcc/6.2.0/bin/../include/c++/6.2.0/type_traits(2512): error: class "std::enable_if<<error-constant>, int>" has no member "type"
      using enable_if_t = typename enable_if<_Cond, _Tp>::type;
                                                          ^
          detected during instantiation of type "std::enable_if_t<<error-constant>, int>" at line 30 of "main.cpp"

main.cpp(62): error: more than one instance of overloaded function "B::y" matches the argument list:
            function template "void A<T>::y(R) [with T=B]"
            function template "void A<T>::y(R) [with T=B]"
            argument types are: (B)
            object type is: B
    b.y(b);

I was however able to compile the following with both the Intel compiler and GCC.

#include <iostream>

#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0

template<class T, class B, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f(T*) -> std::true_type;

template<class T, class B>
auto has_x_f(B*) -> std::false_type;

template<class T, class B>
using has_x = decltype(has_x_f<T, B>((T*)nullptr));

template<class T>
class A
{
   public:
      T& self() { return static_cast<T&>(*this); }

      void x() { }

      template
         <  class TT = T
         ,  typename std::enable_if<has_x<TT, A<TT> >::value, int>::type = 0
         >
      void y()
      {
         std::cout << " have x hidden " << std::endl;
         // if you are so inclined, you can call x() in a "safe" way
         this->self().x(); // Calls x() from class "Derived" (Here class B)
      }

      template
         <  class TT = T
         ,  typename std::enable_if<!has_x<TT, A<TT> >::value, int>::type = 0
         >
      void y()
      {
         std::cout << " does not have x hidden " << std::endl;
         // if you are so inclined, you can call x() in a "safe" way
         this->self().x(); // Calls x() from class "Base" (Here class A)
      }
}; 

class B : public A<B>
{
   public:
      void x() { }
}; 

class C : public A<C>
{
};


int main()
{
   B b;
   C c;

   b.y();
   c.y();

   return 0;
}

I am not aware whether or not this is incorrect according to the standard however, but as I see it you do not run into the problem mentioned in one of the other answers, that you have a template that cannot be instantiated.


EDIT: I was able to get to compile on MSVC 2017 by some "old-times" template metaprogramming tricks, and using classes instead of functions. If I use this implementation of has_x instead it compiles:

template<class T, bool>
struct has_x_impl;

template<class T>
struct has_x_impl<T, true>: std::true_type
{
};

template<class T>
struct has_x_impl<T, false>: std::false_type
{
};

template<class T>
using has_x = has_x_impl<T, std::is_same<void(T::*)(), decltype(&T::x)>::value>;

Full code on Wandbox here.

like image 22
Banan Avatar answered Nov 01 '22 01:11

Banan