【问题标题】:Check if type is defined检查类型是否已定义
【发布时间】:2016-10-02 12:00:56
【问题描述】:

考虑这个例子:

#include <iostream>
#include <type_traits>

template <class, class = void>
struct is_defined : std::false_type
{ };

template <class T>
struct is_defined<T,
    std::enable_if_t<std::is_object<T>::value &&
                    !std::is_pointer<T>::value
        >
    > : std::true_type
{
private:
    static const T test; //try to create incomplete type member
};

struct defined { };

struct forward_declared;

int main()
{
    std::cout << std::boolalpha
              << is_defined<defined>::value << std::endl
              << is_defined<forward_declared>::value << std::endl;
}

两者的输出都是true。我想如果我尝试将struct 设为不完整类型的成员,那么这个模板特化将从重载集中被丢弃。但事实并非如此。删除 static const 会导致不完整类型的编译时错误。这种方法有什么问题,如果可能,如何实现?

【问题讨论】:

  • is_object 不代表is_complete
  • @n.m.那只是为了禁止检查引用。我可以改用std::is_reference,但这不会有太大的不同。
  • 啊,好的,我看到您正试图声明一个静态的东西来清除不完整的类型。也没有工作,你需要 ODR 使用那个静态的东西。

标签: c++ templates typetraits incomplete-type


【解决方案1】:

试试这个:

template <class T>
struct is_defined<T,
    std::enable_if_t<std::is_object<T>::value &&
                    !std::is_pointer<T>::value &&
                    (sizeof(T) > 0)
        >
    > : std::true_type
{
};

【讨论】:

  • 哇,真快 :) 你能解释一下为什么我们可以通过尝试在不完整类型上应用 sizeof 来丢弃重载,但我们不能通过让 T 成为成员来做到这一点?
  • @BlackMoses 这只是您的普通 SFINAE。 sizeof 不适用于不完整的类型,所以替换会失败。
  • 警告:此代码错误。它仅告诉您类型是否在首次实例化is_defined 时定义。结果由编译器缓存,并且将保持其值即使在您定义了类型之后。除非出于某种原因您想要这种行为,否则不要使用它! Example
【解决方案2】:

一般来说,在这种情况下,您可以将一些不接受不完整类型的运算符用于您的 sfinae 表达式。
例如,您可以使用typeid:

#include <iostream>
#include <type_traits>
#include <utility>

template<typename T, typename = void>
constexpr bool is_defined = false;

template<typename T>
constexpr bool is_defined<T, decltype(typeid(T), void())> = true;

struct defined { };
struct forward_declared;

int main()
{
    std::cout << std::boolalpha
              << is_defined<defined> << std::endl
              << is_defined<forward_declared> << std::endl;
}

正如其他人所提到的,另一个有效运算符是sizeof

【讨论】:

  • 警告:此代码错误。它只告诉您类型是否在首次实例化is_defined 时定义。结果由编译器缓存,并且将保持其值即使在您定义了类型之后。除非出于某种原因您想要这种行为,否则不要使用它! Example
【解决方案3】:

关于问题“这种方法有什么问题......” 当我们进行静态 const T 测试时;它不是 is_defined<...> 类的一部分,我们实际上必须在其他地方定义静态成员 is_defined<...>::test ,并且只有在那个“其他”地方我们才需要 T 是完整的类型。 is_defined<...> 类编译只是因为静态成员大小对 is_defined<...> 大小没有影响。

去除静态常量导致T检验;要成为 is_defined<...> 的成员(并影响 is_defined<...> 大小),因此 T 必须是具有已知大小的完整类型。

更新说明:如果在模板特化中声明类成员,它不会被 SFINAE 丢弃,因为这既不是“参数替换”,也不是“SFINAE 表达式”(也不是函数要重载!)。相反,它是某种“SFINAE 字段声明”——而不是标准规定的在替换失败时“不是错误”的内容。

【讨论】:

  • 感谢您的解释。但是,当这种不完整类型是成员时,为什么不会从重载中丢弃模板特化呢?它会导致错误。
  • 在模板特化中声明类成员的情况下,它不会被 SFINAE 丢弃,因为这既不是“参数替换”,也不是“SFINAE 表达式”(也不是要重载的函数!)。相反,它是某种“SFINAE 字段声明”——不是标准规定的在替换失败时“不是错误”的内容。
猜你喜欢
  • 2021-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多