【问题标题】:if-else depends on whether T is a complete typeif-else 取决于 T 是否是完整类型
【发布时间】:2017-11-04 12:33:00
【问题描述】:

在某个.cpp中如何判断某个类型是否为完整类型?

template<class T>class Test{
    //some fields
    void(*functor)(T*) =[](T*){}; 
    //^ will be written by some .cpp that can access T as complete-type 
    T* t=nullptr;
    void fComplete(){    
        delete t;     //faster
        /** ^ some code that use complete type*/    
    }
    void fForward(){
        functor(t);   //slower
        /** ^ some code that forward declaration is enough*/   
    }
    void f(){  
        /*if(T is complete type){    
            fComplete();
        }else fForward();*/
    }
};

demo

当我想过早优化自定义智能指针中的删除功能时,它会很有用。

谁能确认这是不可能的?
我不是在寻求解决方法(但我不介意)——这个问题只是我的好奇心。

【问题讨论】:

  • 我曾希望这会奏效,但遗憾的是它没有...ideone.com/nEGsZu
  • 您可以根据类型是否完整来进行工作,但您的模板必须始终评估相同的内容,否则会违反 ODR。
  • 如果您使用 type trait 评估类型不完整,那么在所有翻译单元中,您的 trait 必须产生相同的结果。
  • @Curious 很抱歉,如果不清楚。这是对问题的评论。尝试在编译时根据类型是否完整进行分支是一个坏主意,尤其是如果该类型在另一个翻译单元中可能是完整的。
  • @Guillaume Racicot 我的直觉也(轻轻地)告诉我这是一个坏主意,但我找不到任何具体原因。如果您不介意,请您分享一些原因吗?

标签: c++ templates c++14 incomplete-type


【解决方案1】:

这行得通

#include <iostream>
#include <type_traits>

using namespace std;

class Incomplete;
class Complete {};

template <typename IncompleteType, typename = std::enable_if_t<true>>
struct DetermineComplete {
    static constexpr const bool value = false;
};

template <typename IncompleteType>
struct DetermineComplete<
        IncompleteType,
        std::enable_if_t<sizeof(IncompleteType) == sizeof(IncompleteType)>> {
    static constexpr const bool value = true;
};

int main() {
    cout << DetermineComplete<Complete>::value << endl;
    cout << DetermineComplete<Incomplete>::value << endl;
    return 0;
}

注意我喜欢使用std::enable_if_t 来获得与void_t 相同的效果,直到它可用,而不是自己到处编写实现。

注意请查看其他关于 ODR 的答案。他们提出了一个有效的观点,你应该在使用它之前考虑一下。

【讨论】:

  • 现在在两种情况下评估它,一种是完整的,另一种是不完整的,我怀疑由于 ODR 违规,您的程序格式不正确,不需要诊断。我相信这甚至适用于交叉编译单元。所以“工作”似乎有点牵强。
  • @Yakk 注释已添加
【解决方案2】:

C++ 中有一条称为 ODR 的规则。这条规则的基本原理(根据我的理解)是某事物可以有任意多的声明,但只有一个定义。看起来很简单,但是有了模板和内联函数,就很容易破解了。

使用模板,多重定义是不可避免的。相同模板的实例化将发生在使用它的所有翻译单元中。这似乎违反了单一定义规则,但对于内联和模板化实体,该规则得到了扩展。这是关于 cppreference 的一段:

一个程序中可以有多个定义,只要每个 定义出现在不同的翻译单元中,每个 以下:类类型、枚举类型、带外部的内联函数 带有外部链接的内联变量(C++17 起),类 模板,非静态函数模板,类的静态数据成员 模板,类模板的成员函数,部分模板 专业化,只要满足以下所有条件:

  • 每个定义都由相同的标记序列组成(通常出现在同一个头文件中)

  • 从每个定义中查找名称会发现相同的实体(在重载解析之后),除了具有内部或 没有链接可以引用不同的对象,只要它们不是 使用 ODR 并且在每个定义中具有相同的值。

  • 重载的运算符,包括转换、分配和解除分配函数从每个函数中引用相同的函数
    定义(除非指定义中的定义)

  • 语言链接相同(例如,包含文件不在 extern "C" 块内)

  • 以上三个规则适用于每个定义中使用的每个默认参数

  • 如果定义是针对具有隐式声明的构造函数的类,则使用 odr 的每个翻译单元都必须调用 基类和成员的构造函数相同

  • 如果定义是针对模板的,那么所有这些要求都适用于定义点的名称和从属名称 实例化点

如果满足所有这些要求,程序的行为就像 整个程序中只有一个定义。否则,该 行为未定义。

简而言之,如果任何功能模板在某些翻译单元中扩展为略有不同的东西,那么您最终会进入 UB 领域。相信我,调试 ODR 违规是最糟糕的,因为您的程序可能会运行很长时间,并且在更改某些编译选项(例如优化)时突然崩溃。

在您的特定情况下,您想检测类型是否完整以更改函数的定义。由于在某些地方您可能有一个完整的类型实例化该函数,因此您最终会得到该函数的多个不同定义。

也要小心宏。如果某些宏定义仅在某些翻译中发生更改,并且您在模板或内联函数中使用该宏,则违反了 ODR,因为该函数不会包含完全相同的标记。


现在,我承认其他答案也确实有用。检测一个类型是否完整并不是完全没用的。我在我的代码中使用它。我用它通过static_assert 提供很好的诊断,甚至STL 的一些实现也可以做到(GCC 的STL 中的unique_ptr 析构函数)。

【讨论】:

  • 我认为你不需要函数模板:is_incomplete&lt;T&gt; 的检测习语必须是每个翻译单元中的 *same8。这意味着合法的方法是它是否到处都不完整,这意味着 OP 的设计存在根本缺陷。
猜你喜欢
  • 2020-04-03
  • 2011-12-06
  • 2015-01-07
  • 1970-01-01
  • 1970-01-01
  • 2018-03-09
  • 1970-01-01
  • 2017-05-12
  • 2014-07-17
相关资源
最近更新 更多