c++ 正在从类模板中排除转换运算符...〈typename T>基于T上的特征

u5rb5r59  于 2023-03-20  发布在  其他
关注(0)|答案(1)|浏览(111)

这个问题涵盖了C++03,以及如何从类模板中省略一个转换操作符,比如template<typename T> struct Foo { ... };,给定T的traits。

  • (底部的问题)*
    背景

理想情况下,我希望使用enable_if构造和SFINAE来排除基于类模板T特性的转换操作符(参见上面的Foo),但默认模板参数不能用于C++03中的函数模板,这(afaik)排除了这种方法;如之前在以下线程中所述:

相反,我使用的方法是转换操作符返回值的类型取决于T的特性,特别是对于某些T类型,它是一个伪类型(void或一些私有的外部不可访问类型)。特别是(C++03)标准在此上下文中所保证的内容。
考虑下面的例子(我已经尽可能地减少了这个例子),使用上一段描述的方法:

include/util.h

namespace util {

// dummy predicate: is T int?
template <typename T> struct is_int { static const bool value = false; };
template <> struct is_int<int> { static const bool value = true; };
template <typename T> const bool is_int<T>::value;

// [meta.trans.other]/conditional
template <bool B, class T, class F> struct conditional { typedef T type; };
template <class T, class F> struct conditional<false, T, F> { typedef F type; };

// class template with non-template operator() member
template <typename T> struct Foo {
    explicit Foo(const T &value) : value_(value) {}

    // [Question regarding this conversion operator here]
    operator typename conditional<is_int<T>::value, int, void>::type() const {
        return value_;
  }

private:
  T value_;
};

/* Alternatively */
template <typename T> class Bar {
    struct Dummy {};
    T value_;
public:
    explicit Bar(const T &value) : value_(value) {}
    operator typename conditional<is_int<T>::value, int, Dummy>::type() const {
        return value_;
  }
};

}  // namespace util

main.cc

#include "include/util.h"

void baz(int) {}

int main()
{
    const util::Foo<int> foo_int(42);
    baz(foo_int); // OK
    const util::Foo<char> foo_char('a');

    const util::Bar<int> bar_int(42);
    baz(bar_int); // OK
    const util::Bar<char> bar_char('a');

    /* OK, expected:
       Error: cannot convert ‘const util::Foo<char>’/‘const util::Bar<char>’
         to ‘int’ for argument ‘1’ to ‘void baz(int)
    baz(foo_char);
    baz(bar_char); */

    return 0;
}

使用gccclang-std=c++03)可以很好地编译,但是我想知道有条件地为转换操作符使用一个无效的主体(/return)是否真的可以,例如Foo<char>的完整示例化。[温度 Jmeter ]/1(14.7.1 in the C++03 standard draft)描述了[强调我的]:
类模板专门化的隐式示例化导致类成员函数**、成员类、静态数据成员和成员模板的声明的隐式示例化,而不是定义或缺省参数的隐式示例化;并且它导致成员匿名联合的定义的隐式示例化。除非类模板的成员 * 或成员模板已经被显式示例化或显式专门化,否则当在要求成员定义存在的上下文中引用该专门化时**,该成员的专门化被隐式示例化 *;

问题

  • C++03标准是否保证类模板的条件(T上的trait)无效的非模板成员函数体不是错误,如果它不是从类的示例化引用的,在那里它是/将是无效的?
esyap4oy

esyap4oy1#

你的推理是正确的。
根据所引用的段落([temp.inst]/1),当Foo<T>对于某些Foo被 * 隐式地 * 示例化时,这并不示例化成员函数的定义,并且成员函数定义直到需要该定义存在时才被示例化。
定义必须存在于一个定义规则下,具体为[basic.def.odr]/3下:
每个程序应包含该程序中使用的每个非内联函数或对象的一个定义;不需要诊断。定义可以显式出现在程序中,也可以在标准库或用户定义库中找到,或者(适当时)隐式定义(见12.1、12.4和12.8)。内联函数应在使用它的每个翻译单元中定义。
[basic.def.odr]/2解释了“使用”哪些函数:
[...]如果一个对象或非重载函数的名称出现在一个可能求值的表达式中,则该对象或非重载函数是 used。如果它不是pure,则使用虚成员函数。如果从一个可能求值的表达式引用时,重载函数被重载解析选择,则使用重载函数。[* 注意:* 这涵盖了对命名函数的调用(5.2.2)、运算符重载(第13条)、用户定义的转换(12.3.2)、用于放置new的分配函数(5.3.4)以及非默认初始化(8.5)。即使实现实际上省略了调用,也使用复制构造函数。] [...]
(NB:在C++11中,这个概念被重新命名为“odr-use”。
当你仅仅创建一个util::Foo<char>util::Bar<char>类型的对象时,并不需要使用该类可能有的任何转换函数,因此在One Definition规则下不需要定义。最后,[temp.inst]/9承诺:
一个实现不应该隐式示例化一个函数模板、一个成员模板、一个非虚成员函数、一个成员类或者一个不需要示例化的类模板的静态数据成员。
所以你是安全的:则不示例化格式不正确的主体。
另一种有条件声明转换函数的方法是从一个CRTP基类继承,这个基类可能有也可能没有转换函数,看起来像这样(我还没有编译它,所以它可能包含错误):

template <typename T, bool b>
struct Bar {};

template <typename T>
struct Bar<T, true> {
    operator int() const {
        return static_cast<const T*>(this)->value_;
    }
};

template <typename T> struct Foo : Bar<Foo, is_int<T>::value> {
    explicit Foo(const T &value) : value_(value) {}
    
    friend class Bar<Foo, is_int<T>::value>;

private:
  T value_;
};

相关问题