Почему это вызов функции с использованием GCC? Шаблонный вывод не удался? - PullRequest
0 голосов
/ 17 января 2019

Я не могу скомпилировать мой текущий, на мой взгляд, действительный код C ++ (17) ни с помощью GCC, ни clang.

Недавно мне удалось вызвать ошибку, компилирующую мой (на мой взгляд) действительный код C ++ 17 с clang (Отчет об ошибке: https://bugs.llvm.org/show_bug.cgi?id=40305). Позже я изменил код и получил ошибку, пытаясь скомпилировать код с GCC.

Мне удалось выделить проблемные части кода и найти возможные обходные пути для обоих компиляторов, которые также работают в моем реальном коде:

#include <iostream>
#include <utility>

template<class T, int... Ns>
class Tensor
{
};

template<class T, int... Ns>
class Base
{
};

template<class T, int... Ns>
class Derived : public Base<T, Ns...>
{
};

template<class T, int... Ns>
decltype(auto) convert(Base<T, Ns...> const &a)
{
    return a;
}

template<class T, int... Ns>
auto convert(Tensor<T, Ns...> const &)
{
    return Derived<T, Ns...>();
}

#ifdef WGCC1 // First work-around for GCC
template<class T, int... Ns>
void error(Base<T, Ns...> const &arg)
{
    std::cout << "Function" << std::endl;
}
#endif

template<class... Ts, int... Ns>
void error(Base<Ts, Ns...> const &... args)
{
    std::cout << "Function" << std::endl;
}

template<class... Ts
#ifdef WGCC2 // Second work-around for GCC
    >
#else
    ,
    class = std::enable_if_t<
        std::conjunction<std::is_class<std::remove_reference_t<Ts>>...>{}>>
#endif
void error(Ts &&... args)
{
    std::cout << "Wrapper: ";
    ((std::cout << typeid(args).name() << " "), ...);
    std::cout << std::endl;
#ifdef WCLANG // Work-around for clang, see:
              // https://bugs.llvm.org/show_bug.cgi?id=40305
    return error(convert(convert(std::forward<Ts>(args)))...);
#else
    return error(convert(std::forward<Ts>(args))...);
#endif
}

int main()
{
    Tensor<int, 4, 4> a;
    error(a);
}

См .: https://godbolt.org/z/L5XVgL

Примечание. Очевидно, что этот код больше не имеет смысла, т. Е. Проверка SFINAE для std :: is_class. Однако проблема та же, что и в моем значимом, реальном коде.

Результаты:

  • clang без обходного пути вызывает внутреннюю ошибку компилятора.
  • GCC без обходного пути приводит к неоднозначному вызову функции
error(const Base<T, Ns...>&)

Я действительно ожидаю, что никакого обходного пути не потребуется. Clang должен быть в состоянии определить параметры шаблона. Я не вижу, как GCC приходит к мысли, что вызов функции неоднозначен, на мой взгляд, это очень ясно. Я делаю что-то не так или предполагаю, что сработали шаблоны, которые не будут работать в соответствии со стандартом C ++ (17)?

Другое неожиданное поведение: Если включен один из обходных путей в GCC, функция-оболочка вызывается дважды. Я не уверен, что это ожидается. Или, другими словами: я не уверен, что сначала производится вывод шаблона или преобразование в базу. Clang, похоже, здесь другого мнения, чем GCC. То есть сначала выполняется преобразование в ссылку на базу, однако впоследствии вывод шаблона не выполняется (см. отчет об ошибке). Какой из них прав?

Обновление: исправлена ​​ошибка и для GCC: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88907

Ответы [ 2 ]

0 голосов
/ 17 января 2019
  • Позвольте упростить задачу для gcc до:

    template<class... Ts, int... Ns>
    void error(Base<Ts, Ns...> const &... args) {} // #1
    
    template<class... Ts,
        std::enable_if_t<
            std::conjunction<std::is_class<std::remove_reference_t<Ts>>...>{}, int> = 0>
    void error(Ts &&... args) {} #2
    
    int main()
    {
        const Base<int, 4> b;
    
        error(b); // call #1
    }
    

    Следуя (сложным) правилам overload_resolution

    у нас есть оба метода как жизнеспособные функции, оба шаблона, поэтому мы используем более специализированный шаблон

    Я (как clang) понимаю, что # 1 более специализирован, чем # 2, но не для gcc

    Демо

    Я бы сказал, gcc bug.

  • Позвольте упростить задачу для лязга до:

    template<class... Ts, int... Ns>
    void error(Base<Ts, Ns...> const &... args) {} // #1
    
    template<class... Ts,
        std::enable_if_t<
            (... && std::is_class<std::remove_reference_t<Ts>>::value), int> = 0>
    void error(Ts&&... args) {} // #2
    
    int main()
    {
        Derived<int, 4, 4> d;
        error(d); // call #2
    }
    

    ICE - это всегда ошибка, поэтому ошибка clang.

    Для разрешения # 2 - точное совпадение, тогда как # 1 требует преобразования производного класса в его базу.

    gcc соглашается, что # 2 называется.

    Демо

0 голосов
/ 17 января 2019

Я не знаю, приемлемо ли это для вас, но я предлагаю другой обходной путь для вашей исходной проблемы: преобразуйте в параметр template-template также контейнер.

Я имею в виду ... вместо

template <class... Ts, int... Ns>
void error(Base<Ts, Ns...> const &... arg)
 { }

Я предлагаю

template <template <typename, int...> class C, typename ... Ts, int ... Ns>
void error(C<Ts, Ns...> const &...)
 { }

Если вы хотите быть уверены, что C получено из Base, вы можете навязать это через SFINAE;используя свертывание шаблонов в C ++ 17, вы можете написать

template <template <typename, int...> class C, typename ... Ts, int ... Ns>
std::enable_if_t<(... && std::is_base_of_v<Base<Ts, Ns...>, C<Ts, Ns...>>)> 
      error(C<Ts, Ns...> const &...)
 { }

Вы также можете преобразовать C в вариационный список шаблонов-шаблонов и принять сочетание Base и Derived

template <template <typename, int...> class ... Cs, typename ... Ts, int ... Ns>
std::enable_if_t<(... && std::is_base_of_v<Base<Ts, Ns...>, Cs<Ts, Ns...>>)>
      error(Cs<Ts, Ns...> const &...)
 { }

Ниже приведен пример полной компиляции (как g ++, так и clang ++)

#include <type_traits>

template <typename, int...>
class Base {};

template <typename, int...>
class Wrong {};

template <typename T, int... Ns>
class Derived : public Base<T, Ns...> {};


template <template <typename, int...> class ... Cs, typename ... Ts, int ... Ns>
std::enable_if_t<(... && std::is_base_of_v<Base<Ts, Ns...>, Cs<Ts, Ns...>>)>
      error(Cs<Ts, Ns...> const &...)
 { }

int main ()
 {
   Base<int, 1, 2, 3> a;
   Base<long, 1, 2, 3> b;

   error(a, b);

   Derived<int, 1, 2, 3> c;
   Derived<long, 1, 2, 3> d;

   error(c, d);

   error(a, c, b, d);

   Wrong<int, 1, 2, 3> e;
   Wrong<long, 1, 2, 3> f;

   //error(e, f); // compilation error  
   //error(a, c, e, b, d, f); // compilation error
 } 
...