【问题标题】:Absence of typeof operator in C++03?C++03 中没有 typeof 运算符?
【发布时间】:2011-05-30 20:16:14
【问题描述】:

我只是想知道 boost 是如何实现 BOOST_TYPEOF(在 C++03 中)的,这似乎是一个非常有用的工具。有人知道吗?

另外,我认为 C++03 本身可以提供 typeof 运算符,尤其是当它已经有 sizeof(expr) 时,必须知道 类型 expr 也一样,否则它怎么能告诉我们 size,而不知道 type?真的有可能知道 size,不知道表达式的类型

如果它不知道type,那么编译器会告诉我们size是什么(如果不是type)?我的意思是,sizeof(unknowntype) 对编译器(以及人类)没有意义!

【问题讨论】:

  • C++03 可以提供了很多东西。但是标准已经巨大了,而且他们并不热衷于添加超过绝对必要的内容。所以他们遗漏了很多东西,他们自己承认,拥有这些东西会很高兴。当然,C++0x 给你decltype 解决了这个问题。
  • @jalf : 我所说的 "C++03 本可以提供..." 的意思是编译器已经实现了sizeof(),除非它实现,否则这是不可能的清楚地发现表达式的类型。这意味着,为了提供typeof() 运算符,编译器不需要做额外的事情。事实上,typeof()sizeof() 一起免费提供,因为我认为后者比前者需要更多的检查/分析/工作。
  • 没有所谓的“免费”。至少,还有额外的测试要做。但你也低估了复杂性。 在简​​单的情况下,获取表达式的类型很简单,但是您需要做出很多决定,例如是否保留 CV 限定符和引用。 decltype 有一些奇怪的规则来解决这个问题。但其次,获取类型是容易的部分。然后你需要弄清楚它应该如何与语言的其他部分交互。是否应该允许您这样做:std::vector<typeof(4)>?这不是“免费的”
  • 但我的观点仍然成立。是的,它可以被实现并且它有用,但它会使语言变得更大更复杂,编译器已经花了将近十年的时间赶上标准,因为它是。他们必须在某处划清界限。他们还扼杀了 STL,只留下了大约三分之二的内容,仅仅是因为他们无法让语言变得太大。
  • 只是为了说明,sizeof 丢弃了引用。如果您在 int& 类型的变量上调用 sizeof,它会为您提供 int 的大小。但是同一变量上的typeof 应该返回int 还是int&

标签: c++ templates boost metaprogramming typeof


【解决方案1】:

它只是使用编译器魔法。比如,GCC 的__typeof__。对于不提供这种魔法的编译器,它提供了一个模拟,可以检测 some 表达式的类型,但会因完全未知的类型而失败。

一个可能的实现可能是拥有一个接受给定类型表达式的函数列表,然后使用类模板从该类型分派到一个数字。为了使函数模板以编译时实体的形式返回数字,我们将其放入数组维度中

template<typename> struct type2num;
template<int> struct num2type;
template<typename T> typename type2num<T>::dim &dispatch(T const&);

然后从那个数字返回到类型,这样我们的EMUL_TYPEOF就可以直接命名类型。所以要注册一个类型,我们写

#define REGISTER_TYPE(T, N) \
  template<> \
  struct type2num<T> { \
    static int const value = N; \
    typedef char dim[N]; \
  }; \
  template<> \
  struct num2type<N> { typedef T type; }

有了这个,你就可以写了

#define EMUL_TYPEOF(E) \
  num2type<sizeof dispatch(E)>::type

只要你需要注册一个类型,你就写

REGISTER_TYPE(int, 1);
REGISTER_TYPE(unsigned int, 2);
// ...

当然,现在你发现你需要一种机制来接受vector&lt;T&gt;,而你事先并不知道T,然后它变得任意复杂。您可以创建一个系统,其中数字不仅仅意味着一种类型。这可能有效:

#define EMUL_TYPEOF(E) \
  build_type<sizeof dispatch_1(E), sizeof dispatch_2(E)>::type

这可以检测像 int 这样的类型以及像 shared_ptr&lt;int&gt; 这样的类型 - 换句话说,不是类模板特化的类型,以及具有一个模板参数的类模板特化,通过进行某种系统映射

  • 如果第一个数字产生 1,则第二个数字指定类型;否则
  • 第一个数字指定模板,第二个数字指定第一个类型的模板参数

这样就变成了

template<int N, int M>
struct build_type {
  typedef typename num2tmp<N>::template apply<
    typename num2type<M>::type>::type type;
};

template<int N>
struct build_type<1, N> {
  typedef num2type<N>::type type;
};

我们还需要更改dispatch 模板并将其拆分为两个版本,如下所示,以及用于注册单参数模板的REGISTER_TEMP1

template<typename T> typename type2num<T>::dim1 &dispatch_1(T const&);
template<typename T> typename type2num<T>::dim2 &dispatch_2(T const&);

#define REGISTER_TYPE(T, N) \
  template<> \
  struct type2num<T> { \
    static int const value_dim1 = 1; \
    static int const value_dim2 = N; \
    typedef char dim1[value_dim1]; \
    typedef char dim2[value_dim2]; \
  }; \
  template<> \
  struct num2type<N> { typedef T type; }

#define REGISTER_TMP1(Te, N) \
  template<typename T1> \
  struct type2num< Te<T1> > { \
    static int const value_dim1 = N; \
    static int const value_dim2 = type2num<T1>::value_dim2; \
    typedef char dim1[value_dim1]; \
    typedef char dim2[value_dim2]; \
  }; \
  template<> struct num2tmp<N> { \
    template<typename T1> struct apply { \
      typedef Te<T1> type; \
    }; \
  }

注册std::vector 模板和int 变体现在看起来像

REGISTER_TMP1(std::vector, 2);
// ... REGISTER_TMP1(std::list, 3);

REGISTER_TYPE(int, 1);
REGISTER_TYPE(unsigned int, 2);
// ... REGISTER_TYPE(char, 3);

您可能还想为每种类型注册多个数字,为​​每个 const/volatile 组合注册一个数字,或者每种类型可能需要多个数字来记录 *&amp; 等。您还想支持vector&lt; vector&lt;int&gt; &gt;,因此模板参数也需要多个数字,使build_type 递归调用自身。由于您可以创建任意长的整数列表,因此无论如何您都可以将任何内容编码到该序列中,因此如何表示这些内容取决于您的创造力。

最后,您可能正在重新实现 BOOST_TYPEOF :)

【讨论】:

  • 您为进一步思考提供了巨大的动力。谢谢:-)
【解决方案2】:

根据记忆,boost::typeof 是通过一些 ?: hacks 实现的。首先,您从一个可以转换为任何其他类的类开始,例如

class something {
public:
    template<typename T> operator const T&() {
        return *(T*)0;
    }
};

?: 规则规定,如果双方具有相同的类型,则结果就是该类型。否则,如果一种类型可以转换为另一种类型,那就是结果类型。所以通过这样做

true ? something() : expr;

结果类型是 expr- 的类型(一个 const 引用),但 expr 从未被实际计算过,因为它位于 false 分支上。那么你把它传递到某个已经有参数推导的地方,比如函数参数。

template<typename T> void x(const T& t) {
    // T is the type of expr.
}

这有点复杂,因为从内存来看,C++03 没有引用折叠,所以它可能比这个例子更复杂——最有可能使用 SFINAE 和类型特征。

如何将其转换为可以传递给模板的实际编译时类型,我不知道。至于 C++03 提供 decltype(),嗯,C++03 语言的问题要大得多,比如 ODR 和声明/定义顺序,没有移动语义等,比不提供 decltype 要糟糕得多。

【讨论】:

  • BTW-您能否详细说明 ODR 是 C++ 的问题?我一直认为这是一个自然规律,但我可能会遗漏一些东西。
  • @DeadMG :你的最后一段对我来说没有意义。主题是关于typeof。所以当编译器已经知道表达式的typesize,那么很明显它也知道type了。
  • @Kos:因为你一生都在告诉编译器它已经知道但碰巧在其他翻译单元中的东西,或者必须声明然后定义,或者任何类似的老废话,这是程序员时间的巨大浪费。 @Nawaz:我的意思是 C++,03 或 0x,已经有这么多的 wtfs,在 03 中不提供 decltype 在“委员会抽什么烟?”列表中的位置很低。
  • @DeadMG,我相信您提到了在许多翻译单元中重新声明任何内容的必要性(我确实讨厌);这与单一定义规则有什么关系?我们谈论的是同一个 ODR 吗? :)
  • @DeadMG,在这种情况下,我想说我们提到的两个问题都归于一个名称:“C/C++ 有翻译单元而不是模块”。
【解决方案3】:

定义为:

#define BOOST_TYPEOF(Expr) \
boost::type_of::decode_begin<BOOST_TYPEOF_ENCODED_VECTOR(Expr) >::type

我不知道细节,但它似乎在某种表格中查找类型,并扩展为(表格条目)::type

不,不知道类型就不可能知道大小。只是没有人想到将 typeof 添加到 C++03 中。

【讨论】:

    【解决方案4】:

    由于 boost 是作为源代码分发的(并且可用的 BOOST_TYPEOF 在任何情况下都是一个头文件实现),你当然可以看看。它有很多特定于编译器的条件编译,所以答案是它是特定于编译器的。

    【讨论】:

      【解决方案5】:

      虽然与typeof 不完全相同,但C++0x 有decltype

      【讨论】:

      • decltype 和 typeof 怎么不一样?
      • 我知道 C++0x 有 decltype 但这不是这里的问题。你应该在 cmets 中写这样的东西。
      猜你喜欢
      • 2011-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-03
      • 1970-01-01
      • 1970-01-01
      • 2010-10-24
      • 1970-01-01
      相关资源
      最近更新 更多