我认为@max66彻底回答了这里发生的事情;initializer_list 构造函数是贪婪的,所以我们必须小心。
回答你的第二个问题:
无论它是聚合类型、用户定义的构造函数还是 initializer_list 构造函数,我如何才能正确检测是否return {some, args};
会在返回的函数中工作?T
std::is_constructible
通常是去这里的方式,但是它只检查括号构造是否有效,所以在你的情况下,以下static_assert
失败:
static_assert(std::is_constructible<MyType, char, char, char>::value, "");
此外,即使它确实有效,也无法判断我们是否需要使用花括号或常规括号来执行初始化。
所以,让我们别名is_constructible
更具体is_paren_constructible
:
template<class T, class... Args>
using is_paren_constructible = std::is_constructible<T, Args...>;
template<class T, class... Args>
constexpr bool is_paren_constructible_v =
is_paren_constructible<T, Args...>::value;
请注意,我将在此答案中使用 C++14 和 C++17 功能,但我们可以仅使用 C++11 完成相同的事情。
现在让我们也区分列表初始化和另一个特征,is_list_constructible
. 为此,我将使用 voider-pattern(std::void_t
在 C++17 中引入以帮助实现这一点,但我自己将其定义为更像 C++11):
struct voider{
using type = void;
};
template<class... T>
using void_t = typename voider<T...>::type;
template<class T, class Args, class=void>
struct is_list_constructible : std::false_type{};
template<class T, class... Args>
struct is_list_constructible<T, std::tuple<Args...>,
void_t<
decltype(T{std::declval<Args>()...})
>
>: std::true_type{};
template<class T, class... Args>
constexpr bool is_list_constructible_v =
is_list_constructible<T, std::tuple<Args...>>::value;
这使您的testInit
功能有些奇怪。我们应该使用括号构造还是列表初始化?我们总是可以把它分成两部分...
template<class T, class... Args>
auto listInit(Args&&... args) -> decltype(T{std::forward<Args>(args)...});
static_assert(std::is_same<MyType, decltype(listInit<MyType>('0', 'a'))>::value, "");
template<class T, class... Args>
auto parenInit(Args&&... args) -> decltype(T(std::forward<Args>(args)...));
static_assert(std::is_same<MyType, decltype(parenInit<MyType>(1UL, 'a'))>::value, "");
但这并不好玩,我们宁愿有一个“只是做正确的事”的单一入口点,所以让我们创建一个新函数,do_init
它将首先尝试列表初始化(在编译时),如果失败,将尝试括号初始化:
template<class... Args>
MyType do_init(Args&&... args)
{
constexpr bool can_list_init = is_list_constructible_v<MyType, Args...>;
constexpr bool can_paren_init = is_paren_constructible_v<MyType, Args...>;
static_assert(can_list_init || can_paren_init, "Cannot initialize MyType with the provided arguments");
if constexpr(can_list_init)
return MyType{std::forward<Args>(args)...};
else
return MyType(std::forward<Args>(args)...);
}
在main
我们可以调用我们的do_init
函数,它会MyType
以适当的方式构造(否则失败一个 static_assert ):
int main(){
(void)do_init('a', 'b'); // list init
(void)do_init(10000UL, 'c'); // parenthetical
(void)do_init(1UL, 'd'); // parenthetical
(void)do_init(true, false, true, false); // list init
// fails static assert
//(void)do_init("alpha");
}
我们甚至可以将is_list_constructible
和组合is_paren_constructible
成一个特征is_constructible_somehow
:
template<class T, class... Args>
constexpr bool is_constructible_somehow = std::disjunction_v<is_list_constructible<T, std::tuple<Args...>>, is_paren_constructible<T, Args...>>;
用法:
static_assert(is_constructible_somehow<MyType, size_t, char>, "");
static_assert(is_constructible_somehow<MyType, char, char, char>, "");