несогласованное поведение компилятора для пользовательского преобразования в ссылку для шаблонного класса - PullRequest
0 голосов
/ 03 декабря 2018

Допустим, у нас есть шаблонный класс template< typename T> FOO, который оборачивает указатель T, и мы хотим иметь пользовательское преобразование для получения FOO<T const> &, указывающего на создание экземпляра FOO.Следующий код воспроизводителя иллюстрирует проблемы, которые могут возникнуть (обратите внимание, что преобразования указателей существуют для сравнения)

#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 верен, то по какой причине код преобразования (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

, все это работает на Clang!Почему это должно быть?

1 Ответ

0 голосов
/ 03 декабря 2018

Этот «ответ» относится к практической проблеме, а не к тому, какой (clang или gcc) является правильным.Я включил его, так как он может быть полезен для ФП, даже если он недостаточно хорош для ответа на данном этапе.

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 ++ компилируют это "правильно" .

(Как уже отмечали, ваш reintepret_cast s заставляет программу демонстрировать неопределенное поведение.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...