繁体   English   中英

这被认为是有效的c ++ 11或c ++ 14吗? 或者是gcc / clang错了吗?

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

在尝试解决时是否可以判断一个类是否在C ++中隐藏了一个基本函数? ,我生成了这个:

#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;
}

哪个输出我想要的:

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

clanggcc都编译并执行这个“正确”,但vc ++没有(虽然我知道它有问题,它可以正常使用类似于template <typename T> ... decltype(fn(std::declval<T>().mfn())) )。

所以我的问题是,这被认为是有效的还是会在以后破裂? 我也很好奇x_hidden能够在函数中用作模板参数但是不能在using t = std::integral_constant<bool, x_hidden>使用它。 这只是因为此时模板的类型尚未完全声明吗? 如果是这样,为什么使用它可以用于函数声明?

如果x_hidden为false,则此模板不具有模板参数

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

可以实例化,因此您的程序生成错误,无需诊断。 这是一种常见的黑客行为,其违法行为在某些方面可能会明确甚至是合法的。

可能有理由使用has_x_f而不是直接使用is_same子句初始化is_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;
}

编译器可以自由地注意到x_hiddenfalse ,并说“ is_same<T,R>是什么并不重要”,并推断出没有模板参数可以使这个特化有效 然后生成错误。

一个简单的黑客是

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;
}

在那里我们潜行另一个模板参数,通常等于T 现在,编译器必须承认T2通过has_x测试的可能性,并且传递的参数是R 用户可以通过手动传递“错误的” T2来绕过这个。

这可能无法解决所有问题。 这里的标准读起来有点棘手,但有一篇文章指出,如果在y()体内我们假设我们的T本身有x() ,我们仍然违反了有效模板实例化的可能性规则。

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

知道哪些名称是类型名称允许检查每个模板的语法。 如果出现以下情况,该计划格式错误,无需诊断:

  • 没有为模板[...]生成有效的专业化,并且模板未实例化,或者

没有有效的专业

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

如果x_hidden为false,则可以生成。 另一个重载的退出是无关紧要的。

如果使用T2技巧修复它, 如果主体假设T=T2 ,则同样的规则成立。

三个标准中的单词试图不使模板在某些上下文中被实例化,但我不确定这是否使上述代码良好形成。

我尝试使用英特尔C ++编译器(icpc(ICC)17.0.2 20170213)编译代码,并且不会使用以下消息进行编译:

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);

然而,我能够使用英特尔编译器和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;
}

然而,根据标准,我不知道这是否是不正确的,但正如我所看到的那样,你没有遇到其他一个答案中提到的问题,你有一个无法实例化的模板。


编辑:我能够通过一些“旧时代”模板元编程技巧在MSVC 2017上编译,并使用类而不是函数。 如果我使用has_x这个实现,它编译:

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>;

Wandbox的完整代码在这里

我有一些代码清理(摆脱了外线x_hidden声明)并最终得到以下内容。 我也基于@Yakk上面的回答稍微修改了它,以避免[temp.res] / 8使它失效。

#include <type_traits>
#include <iostream>
#include <cassert>

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

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

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

template<class T, class Base>
using has_x = decltype(has_x_f<T, Base>());

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

  static bool constexpr x_hidden() {
      return has_x<T, A<T>>::value;
  }

  void y()
  {
      assert(x_hidden() == y_<T>(nullptr) );
  }

  void y2()
  {
      if constexpr(x_hidden()) {
          typename T::BType i = 1;
          (void)i;
      } else {
          typename T::CType i = 1;
          (void)i;
      }
  }


private:
  template <typename R, typename T2=T, ENABLE_IF(A<T2>::x_hidden())>
  static bool y_(R*)
  {
     std::cout << "x() is hidden" << std::endl;
     return true;
  }

  template <typename R, typename T2=T, ENABLE_IF(!A<R>::x_hidden())>
  static bool y_(T*)
  {
     std::cout << "x() is not hidden" << std::endl;
     return false;
  }
};

struct B : A<B>
{
    void x() {}
    using BType = int;
};

static_assert(std::is_same<decltype(&B::x), void(B::*)()>::value, "B::x is a member of B");

struct C : A<C>
{
    using CType = int;
};

static_assert(std::is_same<decltype(&C::x), void(A<C>::*)()>::value, "C::x is a member of A<C>");

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.y2();
  std::cout << "C: ";
  c.y();
  c.y2();

  return 0;
}

关于wandbox的现场演示 - gcc和clang都很满意。

MSVC 2017抱怨

错误C2064:对于A<T2>::x_hidden()两个用法,当为B继承实例化A<B>时,term不会计算为0参数的函数。

MSVC 2015给出了相同的投诉,然后遭遇了内部编译器错误。 ^ _ ^

所以我认为这是有效的,但是以不愉快的方式练习MSVC的constexpr或模板实例化机制。

根据[expr.unary.op] / 3中的示例, &B::x的类型为void (B::*)() ,并且&C::x的类型为void (A<C>::*)() 因此,第一个has_x_f()将存在时TB ,和第二has_x_f()将存在时TCBaseA<C>

Per [temp.inst] / 2 ,实例化类实例化声明但不实例化成员的定义。 Per [temp.inst] / 3和4 ,成员函数定义(包括模板函数)在需要之前不会实例化。

我们这里的声明目前是不同的,因为使用RT2意味着编译器无法确定&&的任何一个大小的真实性或错误性。

使用不同的参数类型有助于MSVC,否则它们会将它们视为相同模板成员模板函数的重新定义。 我对[temp.inst] / 2的解读说这不是必需的,因为它们只是在我们实例化它们时才重新定义,并且它们不能同时被实例化。 因为我们使用A<T2>::x_hidden()!A<R>::x_hidden() ,所以编译器此时无法知道它们是互斥的。 我不认为有必要这样做以避免[temp.res] / 8 ,简单地使用A<R>::x_hidden()似乎对我来说足够安全。 这也是为了确保在两个模板中, R实际使用。

从那以后,这很容易。 y()表明我们有来自两条路径的正确值。

根据你的用例,你可以使用if constexprx_hidden()来避免y_()所有模板魔法,每个y2()

这避免了@Yakk的答案中描述的[temp.res] / 8的问题,因为有问题的条款[temp.res] /8.1是模板格式错误 ,如果

没有为模板或模板中的constexpr if语句的子语句生成有效的专门化,并且模板未实例化,[...]

因此,只要您为某些 T实例化A<T>::y2() ,那么您就不受此条款的约束。

只要传入“/ std:c ++ latest”编译器标志, y2()方法就可以使用MSVC2017。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM