Разница между оператором преобразования шаблонов между clang 6 и clang 7 - PullRequest
0 голосов
/ 04 октября 2018

У меня есть некоторый код, который использует оператор преобразования шаблона, чтобы найти тип возвращаемого значения функции, найденной через ADL.

Упрощенный код выглядит следующим образом:

#include <type_traits>

template<typename S>
struct probe {
    template<typename T, typename U = S, std::enable_if_t<
        std::is_same<T&, U>::value &&
        !std::is_const<T>::value, int> = 0>
    operator T& ();

    template<typename T, typename U = S&&, std::enable_if_t<
        std::is_same<T&&, U>::value &&
        !std::is_const<T>::value, int> = 0>
    operator T&& ();

    template<typename T, typename U = S, std::enable_if_t<
        std::is_same<T const&, U>::value, int> = 0>
    operator T const& () const;

    template<typename T, typename U = S&&, std::enable_if_t<
        std::is_same<T const&&, U>::value, int> = 0>
    operator T const&& () const;
};

namespace foo {
    struct bar {};

    auto find_me(bar const&) -> int { return 0; } 
}

int main() {
    // That would be inside a template in my code.
    find_me(probe<foo::bar>{});
}

В Clang 6 и GCC,вышеуказанный код компилируется.Однако в Clang 7 он больше не компилируется!

https://godbolt.org/z/Lfs3UH

Как видите, clang 6 разрешает вызов на probe<foo::bar>::operator foo::bar&&<foo::bar, foo::bar&&, 0>(), но clang 7 завершается неудачно, потому что пытаетсяcall probe<foo::bar>::operator const foo::bar&&<const foo::bar, foo::bar&&, 0>()

Какой компилятор прав?Какое правило в стандарте для этого?Это новая ошибка Clang или это исправление?


Есть много случаев, которые я хочу проверить.Не только foo::bar в качестве параметра, но и многие ссылочные типы, такие как этот:

namespace foo {
    struct bar {};

    auto find_me(bar const&) -> int { return 0; } 
    auto find_me(bar&&) -> int { return 0; } 
    auto find_me(bar const&&) -> int { return 0; } 
    auto find_me(bar&) -> int { return 0; } 
}

int main() {
    find_me(probe<foo::bar>{});
    find_me(probe<foo::bar&>{});
    find_me(probe<foo::bar&&>{});
    find_me(probe<foo::bar const&>{});
    find_me(probe<foo::bar const&&>{});
}

Важно разрешить правильный вызов функции.

Вот живой пример всех этих случаев,GCC преуспевает, но не удается лязг: https://godbolt.org/z/yrDFMg

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

Различие в поведении между clang 6/7 и gcc иллюстрируется этим упрощенным примером кода:

#include <type_traits>

struct S{
    template<class T,class=std::enable_if_t<!std::is_const_v<T>>>
    operator T& ();
};

void test() {
    S a;
    const int& i = a; //Accepted by Gcc and clang 6 accept, rejected by clang 7
}

Gcc и Clang 6 принимают код, а clang 7 отклоняет его.

В случае Gcc и T=int, и T=const int считаются случаями.Только для лязга 7 T=const int.Поскольку T=const int отключено, clang 7 отклоняет код.

Согласно [over.match.ref] :

Функции преобразования S иего базовые классы считаются.Те неявные функции преобразования, которые не скрыты в S и дают тип «lvalue ссылка на cv2 T2» (при инициализации lvalue ссылка или rvalue ссылка на функцию) или «cv2 T2» или «rvalue ссылка на cv2 T2» (когдаинициализация ссылки на rvalue или ссылки на lvalue на функцию), где «cv1 T» совместима с «cv2 T2», являются кандидатами на функции.Для прямой инициализации те явные функции преобразования, которые не скрыты в S и дают тип «lvalue ссылка на cv2 T2» или «cv2 T2» или «rvalue ссылка на cv2 T2», соответственно, где T2 имеет тот же тип, что и Tмогут быть преобразованы в тип T с квалификационным преобразованием, также являются функциями-кандидатами.

В нашем случае это означает, что преобразование S в int& или const int& может быть кандидатом.

И [temp.deduct.conv] :

Вывод аргумента шаблона выполняется путем сравнения типа возврата шаблона функции преобразования (назовем его P) стип, который требуется как результат преобразования (назовите его A; см. [dcl.init], [over.match.conv] и [over.match.ref] для определения этого типа), как описано в [temp.deduct.type].

Поэтому я думаю, что допустимы два буквальных чтения:

  1. gcc считает, что результатконверсии не значит результат последовательности преобразования , поэтому он сначала решает, какая последовательность преобразования является приемлемой согласно [over.match.ref], а затем выполняет вывод аргумента шаблона для оператора преобразования для всех возможных последовательностей преобразования.

  2. clang считает, что результат преобразования означает цель последовательности преобразования .И он выполняет вывод аргументов только для T=cont int.

Из того, что я прочитал в стандарте, я не могу сказать, что такое «правильная» интерпретация стандарта.Тем не менее, я думаю, что поведение clang более соответствует выводу аргументов шаблона в целом:

template<class T,class=std::enable_if_t<std::is_const_v<T>>>
void f(T& x);

void test(){
  int i;
  f(i);
  // If considering that the argument type is int caused
  // template argument deduction failure, then template argument
  // deduction would be performed for a const int argument.
  // But template argument deduction succeeds. So T is deduced to int. 
  // Only after this deduction template argument substitution happens.
  // => both gcc and clang reject this code.
  }
0 голосов
/ 04 октября 2018

Я думаю, что это связано с Ошибка 32861 и оригинальный отчет .который, как кажется, был решен в clang 7.

Взяв, например, вторую перегрузку преобразования:

template<typename T, typename U = S&&, std::enable_if_t<
    std::is_same<T&&, U>::value &&
    !std::is_const<T>::value, int> = 0>
operator T&& ();

в clang 6, вычет в T будет T=bar, чтоприводит к истинности std::is_same<T&&, U>::value, но в clang 7 вычет будет T=bar const, и теперь признак больше не сохраняется, перегрузка не добавляется в набор кандидатов.

Обратите также внимание на то, что тот факт, что в clang 7 вычет равен T=bar const, также приведет к ложному выводу !std::is_const<T>::value и будет способствовать снятию перегрузки.

...