Ответ 1
Да, это возможно. Прежде всего, вам нужно решить, хотите ли вы принять только тип, или если вы хотите принять неявно конвертируемый тип. Я использую std::is_convertible
в примерах, потому что он лучше имитирует поведение не templated параметров, например. Параметр long long
принимает аргумент int
. Если по какой-либо причине вам нужен только тот тип, который нужно принять, замените std::is_convertible
на std:is_same
(вам может потребоваться добавить std::remove_reference
и std::remove_cv
).
К сожалению, в C++
сужение преобразования, например. (long long
до int
и даже duble
до int
) являются неявными преобразованиями. И хотя в классической настройке вы можете получать предупреждения, когда это происходит, вы не получаете этого с помощью std::is_convertible
. По крайней мере, не по телефону. Вы можете получить предупреждения в теле функции, если вы выполните такое задание. Но с небольшим трюком мы также можем получить ошибку на сайте вызова с помощью шаблонов.
Так что без дальнейших церемоний здесь идет:
Испытательная установка:
struct X {};
struct Derived : X {};
struct Y { operator X() { return {}; }};
struct Z {};
foo_x : function that accepts X arguments
int main ()
{
int i{};
X x{};
Derived d{};
Y y{};
Z z{};
foo_x(x, x, y, d); // should work
foo_y(x, x, y, d, z); // should not work due to unrelated z
};
Концепция
Не здесь, но скоро. Это будет самое простое, ясное и элегантное решение
template <class From, class To>
concept constexpr bool Convertible = std::is_convertible_v<From, To>;
template <Convertible<X>... Args>
auto foo_x(Args... args) {}
foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // error:
Мы получаем очень приятную ошибку. Особенно
'Кабриолет' не был удовлетворен
сладкий:
error: cannot call function 'auto foo_x(Args ...) [with Args = {X, X, Y, Derived, Z}]' foo_x(x, x, y, d, z); ^ note: constraints not satisfied auto foo_x(Args... args) ^~~~~ note: in the expansion of 'Convertible<Args, X>...' note: 'Convertible<Z, X>' was not satisfied
Работа с сужением:
template <class From, class To>
concept constexpr bool Convertible_no_narrow = requires(From f, To t) {
t = {f};
};
template <Convertible_no_narrow<int>... Args>
auto foo_ni(Args... args) {}
foo_ni(24, 12); // OK
foo_ni(24, 12, 15.2);
// error:
// 'Convertible_no_narrow<double, int>' was not satisfied
С++ 17
Мы используем очень приятное fold выражение:
template <class... Args,
class Enable = std::enable_if_t<(... && std::is_convertible_v<Args, X>)>>
auto foo_x(Args... args) {}
foo_x(x, x, y, d, z); // OK
foo_x(x, x, y, d, z, d); // error
К сожалению, мы получаем менее явную ошибку:
Ошибка вывода аргумента шаблона: [...]
Сужение
Мы можем избежать сужения, но мы должны приготовить признак is_convertible_no_narrowing
(возможно, его по-другому назвать):
template <class From, class To>
struct is_convertible_no_narrowing_impl {
template <class F, class T,
class Enable = decltype(std::declval<T &>() = {std::declval<F>()})>
static auto test(F f, T t) -> std::true_type;
static auto test(...) -> std::false_type;
static constexpr bool value =
decltype(test(std::declval<From>(), std::declval<To>()))::value;
};
template <class From, class To>
struct is_convertible_no_narrowing
: std::integral_constant<
bool, is_convertible_no_narrowing_impl<From, To>::value> {};
С++ 14
Создаем помощник для соединения:
обратите внимание, что в C++17
будет std::conjunction
, но он примет std::integral_constant
аргументы
template <bool... B>
struct conjunction {};
template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
: std::integral_constant<bool, Head && conjunction<Tail...>::value>{};
template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};
и теперь мы можем иметь нашу функцию:
template <class... Args,
class Enable = std::enable_if_t<
conjunction<std::is_convertible<Args, X>::value...>::value>>
auto foo_x(Args... args) {}
foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error
С++ 11
незначительные изменения в версии С++ 14:
template <bool... B>
struct conjunction {};
template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
: std::integral_constant<bool, Head && conjunction<Tail...>::value>{};
template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};
template <class... Args,
class Enable = typename std::enable_if<
conjunction<std::is_convertible<Args, X>::value...>::value>::type>
auto foo_x(Args... args) -> void {}
foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error