消除 std::vector<std::string> 的列表初始化歧义
Disambiguating list initialization for std::vector<std::string>
我的代码中有一个具有类型签名的重载函数:
void foo(std::string);
void foo(std::vector<std::string>);
我希望 foo 的用户能够使用字符串或字符串列表来调用它
//Use case 1
foo("str");
//Use case 2
foo({"str1","str2","str3"});
foo({"str1","str2","str3","str4"});
问题是当调用者将 两个 字符串传入 foo 的初始化列表时。
//Problem!
foo({"str1","str2"});
这个对 foo 的调用是不明确的,因为它匹配两个类型签名。
这是因为显然 {"str1","str2"}
是 std::string
的有效构造函数
所以我的问题是我可以在 foo 的声明或实现中做些什么来维持我上面描述的 API 而不会遇到这个模棱两可的构造函数情况。
我不想定义我自己的字符串 class,但我可以定义其他东西而不是 vector<string>
,只要它可以用字符串的初始化列表进行初始化。
只是出于好奇,为什么字符串构造函数接受 {"str1","str2"}
?
您可以使用 可变参数模板 稍微更改 API,这可以防止您遇到的歧义。
template <typename... Ts>
auto foo(Ts...)
-> std::enable_if_t<all_are_convertible_to<std::string, Ts...>, void>
{
/* ... */
}
用法:
foo("aaaa");
foo("aaaa", "bbb", "cc", "d");
在C++17中,all_are_convertible_to
可以用折叠表达式实现(或std::conjunction
):
template <typename T, typename... Ts>
inline constexpr bool are_all_convertible =
(std::is_convertible_v<Ts, T> && ...);
在 C++11 中,您可以按如下方式实现某种递归类型特征:
template <typename, typename...>
struct are_all_convertible_to_helper;
template <typename T, typename X, typename... Xs>
struct are_all_convertible_to_helper<T, X, Xs...>
: std::integral_constant<bool,
std::is_convertible<X, T>::value && are_all_convertible_to_helper<T, Xs...>::value
>
{
};
template <typename T>
struct are_all_convertible_to_helper<T> : std::true_type
{
};
{"str1","str2"}
匹配接受两个迭代器的 std::string
构造函数。构造函数 6 here。它会尝试从 "str1" 的开头迭代到 "str2" 的开头,这是未定义的行为。
您可以通过为 std::initializer_list<const char*>
引入重载来解决这种歧义,该重载转发到 std::vector<std::string>
重载。
void foo(std::string);
void foo(std::vector<std::string>);
void foo(std::initializer_list<const char*> p_list)
{
foo(std::vector<std::string>(p_list.begin(), p_list.end()));
}
我的代码中有一个具有类型签名的重载函数:
void foo(std::string);
void foo(std::vector<std::string>);
我希望 foo 的用户能够使用字符串或字符串列表来调用它
//Use case 1
foo("str");
//Use case 2
foo({"str1","str2","str3"});
foo({"str1","str2","str3","str4"});
问题是当调用者将 两个 字符串传入 foo 的初始化列表时。
//Problem!
foo({"str1","str2"});
这个对 foo 的调用是不明确的,因为它匹配两个类型签名。
这是因为显然 {"str1","str2"}
是 std::string
所以我的问题是我可以在 foo 的声明或实现中做些什么来维持我上面描述的 API 而不会遇到这个模棱两可的构造函数情况。
我不想定义我自己的字符串 class,但我可以定义其他东西而不是 vector<string>
,只要它可以用字符串的初始化列表进行初始化。
只是出于好奇,为什么字符串构造函数接受 {"str1","str2"}
?
您可以使用 可变参数模板 稍微更改 API,这可以防止您遇到的歧义。
template <typename... Ts>
auto foo(Ts...)
-> std::enable_if_t<all_are_convertible_to<std::string, Ts...>, void>
{
/* ... */
}
用法:
foo("aaaa");
foo("aaaa", "bbb", "cc", "d");
在C++17中,all_are_convertible_to
可以用折叠表达式实现(或std::conjunction
):
template <typename T, typename... Ts>
inline constexpr bool are_all_convertible =
(std::is_convertible_v<Ts, T> && ...);
在 C++11 中,您可以按如下方式实现某种递归类型特征:
template <typename, typename...>
struct are_all_convertible_to_helper;
template <typename T, typename X, typename... Xs>
struct are_all_convertible_to_helper<T, X, Xs...>
: std::integral_constant<bool,
std::is_convertible<X, T>::value && are_all_convertible_to_helper<T, Xs...>::value
>
{
};
template <typename T>
struct are_all_convertible_to_helper<T> : std::true_type
{
};
{"str1","str2"}
匹配接受两个迭代器的 std::string
构造函数。构造函数 6 here。它会尝试从 "str1" 的开头迭代到 "str2" 的开头,这是未定义的行为。
您可以通过为 std::initializer_list<const char*>
引入重载来解决这种歧义,该重载转发到 std::vector<std::string>
重载。
void foo(std::string);
void foo(std::vector<std::string>);
void foo(std::initializer_list<const char*> p_list)
{
foo(std::vector<std::string>(p_list.begin(), p_list.end()));
}