用户定义转换为模板类参考的编译器行为不一致

时间:2018-12-03 18:19:42

标签: c++ templates language-lawyer implicit-conversion sfinae

让我们说我们有一个模板类template< typename T> FOO,它包装了一个T指针,并且我们希望有一个用户定义的转换,以获取指向FOO实例化的FOO<T const> &。下面的复制器代码说明了似乎出现的问题(请注意,这里有指针转换供比较)

#include<iostream>

template< typename T>
class FOO
{
public:
  template< typename U=T >
  operator typename std::enable_if< !std::is_const<U>::value, FOO<T const> & >::type ()
  {
    std::cout<<"calling FOO<T>::operator FOO<T const>&()"<<std::endl;
    return reinterpret_cast< FOO<T const> &>(*this);
  }

  template< typename U=T >
  operator typename std::enable_if< !std::is_const<U>::value, FOO<T const> * >::type ()
  {
    std::cout<<"calling FOO<T>::operator FOO<T const>*()"<<std::endl;
    return reinterpret_cast< FOO<T const> *>(this);
  }

  T * m_data = nullptr;
};

int main()
{
  FOO<int> foo;

  FOO<int const> & fooToConst  = foo;                                // conversion 1r
  FOO<int const> & fooToConst2  = static_cast<FOO<int const>&>(foo); // conversion 2r
  FOO<int const> & fooToConst3 = foo.operator FOO<int const>&();     // conversion 3r

  FOO<int const> * pfooToConst  = foo;                               // conversion 1p
  FOO<int const> * pfooToConst2 = static_cast<FOO<int const>*>(foo); // conversion 2p
  FOO<int const> * pfooToConst3 = foo.operator FOO<int const>*();    // conversion 3p
  return 0;
}

使用gcc8.1.0 g++ -std=c++14 main.cpp进行编译,一切正常,输出如下:

calling FOO<T>::operator FOO<T const>&()
calling FOO<T>::operator FOO<T const>&()
calling FOO<T>::operator FOO<T const>&()
calling FOO<T>::operator FOO<T const>*()
calling FOO<T>::operator FOO<T const>*()
calling FOO<T>::operator FOO<T const>*()

使用clang6.0 clang++ -std=c++14 main.cpp编译失败,并显示以下信息:

main.cpp:29:20: error: non-const lvalue reference to type 'FOO<const int>' cannot bind to a value of unrelated type 'FOO<int>'
  FOO<int const> & fooToConst  = foo;                                // conversion 1r
                   ^             ~~~
main.cpp:30:35: error: non-const lvalue reference to type 'FOO<const int>' cannot bind to a value of unrelated type 'FOO<int>'
  FOO<int const> & fooToConst2  = static_cast<FOO<int const>&>(foo); // conversion 2r
                                  ^                            ~~~

所有其他转换(3r,1p,2p,3p)都适用于clang。

所以问题是... gcc是正确的,还是clang是正确的?

  • 如果clang是正确的,转换(1r,2r)代码不起作用的原因是什么?

    • 我知道指针转换有些古怪,但是为什么接受(1p,2p)而不能接受(1r,2r)?
    • 为什么gcc允许它们?
  • 如果gcc是正确的,这是clang中的错误吗?

编辑 如果将(1r,2r)的转换尝试更改为:

FOO<int const> const & fooToConst  = foo;                                      // conversion 1r
FOO<int const> const & fooToConst2  = static_cast<FOO<int const> const&>(foo); // conversion 2r

这一切都在c!为什么会这样?

1 个答案:

答案 0 :(得分:1)

此“答案”解决的是实际问题,而不是正确的(c或gcc)。我将其包括在内,因为这可能对OP有所帮助,即使此时这对的回答来说还不够。

template<class X>
struct get_template_arg0;
template<class X>
using get_template_arg0_t=typename get_template_arg0<X>::type;

template<template<class...>class Z, class T>
struct get_template_arg0<Z<T>> {
    using type=T;
};

template< typename T>
class FOO
{
public:
  template< typename U, 
    std::enable_if_t<
        std::is_same<std::remove_const_t<get_template_arg0_t<U>>, T>{}
        && !std::is_same<get_template_arg0_t<U>, T>{},
        bool
    > =true
  >
  operator U& ()
  {
    std::cout<<"calling FOO<T>::operator FOO<T const>&()"<<std::endl;
    return reinterpret_cast< FOO<T const> &>(*this);
  }

  template< typename U, 
    std::enable_if_t<
        std::is_same<std::remove_const_t<get_template_arg0_t<U>>, T>{}
        && !std::is_same<get_template_arg0_t<U>, T>{},
        bool
    > =true
  >
  operator U*()
  {
    std::cout<<"calling FOO<T>::operator FOO<T const>*()"<<std::endl;
    return reinterpret_cast< FOO<T const> *>(this);
  }

  T * m_data = nullptr;
};

我简化了模板转换运算符中类型的推导,然后添加了SFINAE测试。

clang ++和g ++ compile this "correctly"

(正如其他人所指出的那样,您的reintepret_cast使程序表现出不确定的行为。)