在具有显式默认构造函数的类型的复制初始化上下文中,来自 {} 的隐式转换序列

Implicit conversion sequence from {} in a copy-initialization context for a type with an explicit default constructor

(考虑 C++17 及以后的问题)

LWG issue 3562(+),是否可以用 explicit 显式默认取代 nullopt_t 的非 DefaultConstructible 要求其他标签类型的默认构造函数:

struct nullopt_t { explicit nullopt_t() = default; };

因非缺陷 (NAD) 而关闭,理由如下

2021-06-14 Reflector poll:

Current wording prevents an implicit conversion sequence from {}.

(+) 请注意,此问题同时提到了 LWG 问题 2510 和 CWG 问题 1518 和 1630,它们的决议 (changed/superseded) 紧密耦合。

考虑以下示例:

struct my_nullopt_t {
    explicit constexpr my_nullopt_t() = default;
};

struct S {
    constexpr S() {} // user-provided: S is not an aggregate
    constexpr S(S const&)      = default;
    S& operator=(S const&)     = default; // #1
    S& operator=(my_nullopt_t) = delete;  // #2
};

int main() {
    S s{};
    s = {};  // #3
}

我希望它的格式正确,因为重载 #2 对于 #3 处的赋值不可行,因为 my_nullopt_t 的默认构造函数是 explicit (意思是 my_nullopt_t 而且不是 C++17 中的聚合)。我特别认为这是由 [over.match.ctor]/1:

控制的

When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class. [...]

[class.conv.ctor]/1 A constructor that is not explicit ([dcl.fct.spec]) specifies a conversion from the types of its parameters (if any) to the type of its class. Such a constructor is called a converting constructor.

但考虑到反射器上面的评论,我可能错了。可能是[over.match.list]/1

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations ([over.match.ctor], [over.match.copy]), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. — end note ]

通过来自 [over.ics.list]/7?

的条目来管理此案例

我们最终可能会注意到,我们在这里也看到了编译器差异,其中 Clang 接受程序而 GCC 拒绝它(对于 C++11 到 C++20)并出现歧义错误。 GCC 似乎特别认为 #2 是一个可行的重载,而 Clang 则不然。

GCC error: ambiguous overload for 'operator='

DEMO

问题

GCC是对的,Clang和MSVC是错的。这突出显示在:

也作为 NAD 关闭。

有些令人惊讶的是,关于 explicit 构造函数的规则在复制列表初始化 ([over.match.list]) 和复制初始化 ([over.match.copy]) 之间有所不同 [强调我的]:

1228. Copy-list-initialization and explicit constructors

  • Section: 12.2.2.8 [over.match.list]
  • Status: NAD
  • Submitter: Daniel Krügler
  • Date: 2010-12-03

The rules for selecting candidate functions in copy-list-initialization (12.2.2.8 [over.match.list]) differ from those of regular copy-initialization (12.2.2.5 [over.match.copy]): the latter specify that only the converting (non-explicit) constructors are considered, while the former include all constructors but state that the program is ill-formed if an explicit constructor is selected by overload resolution. This is counterintuitive and can lead to surprising results. For example, the call to the function object p in the following example is ambiguous because the explicit constructor is a candidate for the initialization of the operator's parameter:

struct MyList {
  explicit MyStore(int initialCapacity);
};

struct MyInt {
  MyInt(int i);
};

struct Printer {
  void operator()(MyStore const& s);
  void operator()(MyInt const& i);
};

void f() {
  Printer p;
  p({23});
}

Rationale (March, 2011):

The current rules are as intended.