6

我的代码中有一个带有类型签名的重载函数:

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,而不会遇到这种模棱两可的构造函数案例。

我不想定义自己的字符串类,但我可以定义其他东西,而不是vector<string>只要它可以用字符串的初始化列表进行初始化。

只是出于好奇,为什么字符串构造函数接受{"str1","str2"}

4

2 回答 2

5

{"str1","str2"}匹配std::string接受两个迭代器的构造函数。构造函数 6在这里。它会尝试从“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()));
}
于 2017-05-11T16:51:52.357 回答
2

您可以使用可变参数模板稍微更改您的 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
{
};
于 2017-05-11T16:50:57.983 回答