将默认参数值设置为默认构造函数的更好语法

Nicer syntax for setting default argument value to default constructor

可能想要声明一个带参数的函数,并指定参数的默认值是该类型默认构造函数的结果:

void foo(a::really::long::type::name arg = a::really::long::type::name());

是否有更好的语法,不需要输入两次类型名称?类似于:

void foo(a::really::long::type::name arg = default);

我意识到我可以 typedef 类型名称使其更漂亮,但我很好奇是否存在这样的语法。

是:

void foo(a::really::long::type::name arg = {});

总结出以下标准定义:

这是列表初始化。根据类型,执行聚合初始化或对对象进行值初始化,这又意味着默认初始化或零初始化。

一些 "corner" 情况是类型是 std::initializer_list 的特化或类型具有 std::initializer_list 构造函数(如果没有默认构造函数则调用它)


相关标准引述(以我们遇到的顺序定义):

§8.3.6 默认参数 [dcl.fct.default]

1 If an initializer-clause is specified in a parameter-declaration this initializer-clause is used as a default argument

5 The default argument has the same semantic constraints as the initializer in a declaration of a variable of the parameter type, using the copy-initialization semantics (8.5)

§8.5.4 列表初始化 [dcl.init.list]

1 List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is called an initializer list, [...]. An initializer list may be empty. List-initialization can occur in direct-initialization or copy initialization contexts; [..] list-initialization in a copy-initialization context is called copy-list-initialization.

3 List-initialization of an object or reference of type T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed (8.5.1)
  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
  • Otherwise, if T is a specialization of std::initializer_list, a prvalue initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7) [...]
  • ...
  • Otherwise, if the initializer list has no elements, the object is value-initialized.

§ 8.5 初始化器 [dcl.init]

8 To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;
  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized

7 To default-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9), the default constructor (12.1) for T is called (and the initialization is ill-formed if T has no default constructor or overload resolution (13.3) results in an ambiguity or in a function that is deleted or inaccessible from the context of the initialization);
  • if T is an array type, each element is default-initialized;
  • otherwise, no initialization is performed.

6 To zero-initialize an object or reference of type T means:

  • if T is a scalar type (3.9), the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;
  • if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
  • if T is a (possibly cv-qualified) union type, the object’s first non-static named data member is zero-initialized and padding is initialized to zero bits;
  • if T is an array type, each element is zero-initialized;
  • if T is a reference type, no initialization is performed.

§13.3.1.7 通过列表初始化进行初始化 [over.match.list]

1 When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

  • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If the initializer list has no elements and T has a default constructor, the first phase is omitted. [...]

如果您控制 arg 的 class,行人接近是可能的。使用为 enum:

重载的转换构造函数
// Define this enum, and then write constructors which take dfl
enum dfl { dflval };

class a_really_long_type_name {
public:
  a_really_long_type_name(dfl arg = dflval);
};

现在 foo 可以是:

void foo(a_really_long_type_name arg = dflval);

如果你能应用这个,好处就是可移植性;这在一个有 25 年历史的 C++ 编译器中应该可以正常工作。

多个 classes 都可以共享这个 dfl enum 和它的 dflval-flavored 零;就像有了一个新关键字。

因为 enum 是一个独特的类型,这不会干扰整数类型或字符等的构造函数重载。

缺点是将其用于一些 classes 中,这些已经通过参数默认设置提供了默认构造,这会导致重复的构造函数代码。