2

令我惊讶的是,clang 无法编译以下 c++11 代码:

#include <type_traits>

template<class T>
struct A {};

template<class T>
struct B : A<T> { T x; };

template<class T>
const typename std::remove_reference<T>::type& add_const(T&& x) { return x; }

template<class T>
T& cast_away_const(const T& x) { return const_cast<T&>(x); }

template<class T>
const B<T>& downcast(const A<T>& x) { return static_cast<const B<T>&>(x); }

template<class T>
auto downcast(A<T>& a) ->
decltype(cast_away_const(downcast(add_const(a)))) {
    return cast_away_const(downcast(add_const(a)));
}

int main() {
    B<int> b;
    b.x = 42;
    A<int>& a = b;
    B<int>& b_again = downcast(a);
    return b_again.x;
}

更准确地说,clang 3.7.1 抱怨模板实例化链很长,表现得好像auto downcast(A<T>& a) -> /*...*/withT=int的实例化需要自身的实例化。clang 5.0.0 或 6.0.0 只会崩溃(!)。gcc 和 msvc 都接受。

螺栓链接

现在,clang-5.0.0 崩溃显然是一个错误,但 clang-3.7.1 错误是有道理的,所以…… 该代码首先是合法的吗?

让我强调一下,我问的是这段代码是否合法,而不是解决方法(一个简单的解决方法是拆分overloadand overloadoverload_const让后者只处理 const 引用,让前者调用后者)。

我可以在clang和gcc之间找到以下一些相关的争论点(使用带有可变参数模板函数的decltype的尾随返回类型,SO链接)以及这个类似的问题,其答案引用了标准(gcc可以编译可变参数模板而clang不能) 但是,我不确定此处是否适用完全相同的参数(因为downcast应该引用先前声明的重载,而不是尾随返回类型中的本身)。

4

1 回答 1

2

所以第一件事是downcast可以通过 ADL 找到它,A<T>因为它位于关联的命名空间中。

所以在这里:

template<class T>
auto downcast(A<T>& a) ->
decltype(cast_away_const(downcast(add_const(a)))) 

在确定自身的返回类型时必须考虑自身。如果它位于不同的命名空间中,则不允许将其自身视为可能的重载候选者。

所以这是一个简单的解决方案;将两个重载都移到details. A 也许在转发到详细信息版本的同一命名空间中有一个助手 。

显然,msvc 和 gcc 注意到,const&在完全重新评估非常量版本的返回类型之前,重载将是一个更好的选择。

details {
  template<class T>
  const B<T>& downcast(const A<T>& x) { return static_cast<const B<T>&>(x); }

  template<class T>
  auto downcast(A<T>& a) ->
  decltype(cast_away_const(details::downcast(add_const(a)))) {
    return cast_away_const(details::downcast(add_const(a)));
  }
}
template<class X,
  std::enable_if_t< /* A<T> is a base of X for some T */,  bool> =true
>
auto downcast(X&& x)
-> decltype(details::downcast(std::forward<X>(x)))
{ return details::downcast(std::forward<X>(x)); }

我不知道 clang 将代码视为格式错误的程序是否正确。

于 2018-03-14T12:18:21.340 回答