Функция преобразования шаблона в const-reference

Следующее работает отлично (как и следовало ожидать):

struct X {};

struct A
{
  operator X const& () 
  {
    static const X value{};
    return value; 
  }
};

int main()
{
  A a;
  X x = a;
}

Но это не так ясно:

template<typename T>
struct X {};

struct A
{
  template<typename T>
  operator X<T> const& () 
  {
    static const X<T> value{};
    return value; 
  }
};

int main() 
{
  A a;
  X<int> x = a;
}

GCC 4.9 говорит error: conversion from ‘A’ to non-scalar type ‘X<int>’ requested, тогда как clang 3.4 не имеет проблем с ним. Если вы удалите из функции преобразования const или & или напишите X<int> const &x = a, то GCC тоже будет рад.

Таким образом, GCC только не может найти функцию преобразования, если целевой тип - это const & для класса шаблона, и вы запрашиваете преобразование в объект const & этого класса. Это правильное поведение? Я попытался прочитать стандарт, но правила перегрузки меня сбивают с толку.

Ответ 1

Да, это ошибка в gcc. Это почти core DR976, с той лишь разницей, что в их примере тип назначения - тип некласса:

struct F {
   template<class T>
   operator const T&() { static T t; return t; }
};

int main() {
   F f;
   int i = f;   // ill-formed
}

Как и в вашем примере, clang accepts и gcc отклоняют этот пример, независимо от выбранного диалекта стандартной версии.

Обратите внимание, что предложение, измененное этим DR (14.8.2.3 [temp.deduct.conv]), не различает типы назначения класса и некласса, поэтому разрешение этого DR применяется к ваш код в равной степени.

В соответствии с измененным предложением 14.8.2.3 компилятор должен:

  • определить P, тип возврата шаблона функции преобразования, как X<T> const & и A, требуемый тип результата, как X<int>;
  • разделите ссылку от P, указав X<T> const;
  • разделите cv-квалификацию от P, указав X<T>;
  • выводит T как int.

Я бы рекомендовал подать отчет об ошибке на https://gcc.gnu.org/bugzilla/, ссылаясь на этот DR.


Потому что в вашем случае вы переходите к типу класса, существует обходное решение:

X<int> x1 = a;          // fails
X<int> x2(a);           // OK
X<int> x3 = X<int>(a);  // also OK

В прямой инициализации рассматриваются конструкторы типа назначения (8.5p16); конструктор копии по умолчанию X<int> принимает параметр типа X<int> const&, к которому, как мы уже видели, gcc с радостью преобразует A.