Инициализация неоднозначных списков для std :: vector & lt; std :: string & gt;

У меня в коде перегружена функция с сигнатурами типов:

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"}?

2

Решение

{"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()));
}
3

Другие решения

Вы можете немного изменить свой 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
{
};
2

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector