gcc vs. clang:"invalid use of incomplete type" with std::declval 和模板专业化

gcc vs. clang: "invalid use of incomplete type" with std::declval and template specialization

我有一个方法 fun 包含在结构 Impl 中用于部分专业化。检查 is_derived_from_template 用于确定泛型 Impl::fun 是否可以用于派生自特定模板的类型。否则,Impl 是显式部分特化的。

#include <iostream>

template <typename T, typename U>
struct Base{};

// Forward declaration
struct Foo;
struct Bar;

template <template<typename...> class T, typename U>
struct is_derived_from_template
{
private:
    template<typename... Args>
    static decltype(static_cast<const T<Args...>&>(std::declval<U>()), std::true_type{}) test(const T<Args...>&);
    static std::false_type test(...);
public:
    static constexpr bool value = decltype(test(std::declval<U>()))::value;
};

template <typename T, typename = void>
struct Impl
{
    static void fun(T& x);
};

template <typename T>
struct Impl<T, typename std::enable_if<is_derived_from_template<Base, T>::value>::type>
{
    static void fun(T& base)
    {
        std::cout << "Base" << std::endl;
    }
};

template <>
void Impl<Foo>::fun(Foo& t)
{
    std::cout << "Foo" << std::endl;
}

struct Foo {};
struct Bar : Base<int,double> {};

int main()
{
    Foo foo;
    Bar bar;

    Impl<Foo>::fun(foo);

    Impl<Bar>::fun(bar);
}

使用 gcc 编译这段代码时,出现以下错误:

main.cpp: In instantiation of 'constexpr const bool is_derived_from_template<std::vector, Foo>::value':
main.cpp:33:15:   required from here
main.cpp:15:48: error: invalid use of incomplete type 'struct Foo'
     static constexpr bool value = decltype(test(std::declval<U>()))::value;
                                                ^
main.cpp:5:8: note: forward declaration of 'struct Foo'
 struct Foo;
        ^

gcc live demo

但是,clang 没有错误地编译它并且输出符合预期:

Foo
Base

clang live demo

  1. 两个编译器哪个对?
  2. 如何修改我的代码以使其与 gcc 一起工作?

1.Clang 的编译与 GCC 等传统编译器略有不同。与 Clang 相比,GCC 在解析代码 'traditionally' 的意义上是正确的,并且您应该在使用它们之前定义您的类型。
可以找一个比较here

2.Changing:

// Forward declaration
struct Foo;
struct Bar;

至:

struct Foo {};
struct Bar : Base<int,double> {};

对我有用。

减少到

#include <utility>
void f(...);
class C;
using type = decltype(f(std::declval<C>()));

在 Clang 上编译,在 GCC 上出错。

我倾向于说 GCC 就在这里,因为通过 ... 传递一个 class 类型的对象需要复制,而你不能复制不完整类型的东西。

如果您愿意,可以在 SFINAE 中使用指针代替:

template <template<typename...> class T, typename U>
struct is_derived_from_template
{
private:
    template<typename... Args>
    static decltype(static_cast<const T<Args...>&>(std::declval<U>()), std::true_type{}) test(const T<Args...>*);
    static std::false_type test(...);
public:
    static constexpr bool value = decltype(test(std::declval<U*>()))::value;
};

虽然您应该小心允许 is_derived_from_template 使用不完整的类型进行实例化,因为如果完整的类型结果是从指定的模板派生的,它很容易导致 ODR 违规。