Вариантная вариационная функция
Я хочу написать функцию, которая принимает переменное число строковых литералов. Если бы я писал на C, мне пришлось бы написать что-то вроде:
void foo(const char *first, ...);
и тогда вызов будет выглядеть так:
foo( "hello", "world", (const char*)NULL );
Похоже, в C++ должно быть возможно сделать лучше. Лучшее, что я придумал, это:
template <typename... Args>
void foo(const char* first, Args... args) {
foo(first);
foo(args);
}
void foo(const char* first) { /* Do actual work */ }
Вызывается как:
foo("hello", "world");
Но я боюсь, что рекурсивный характер и тот факт, что мы не проводим никаких проверок типов до тех пор, пока мы не дойдем до одного аргумента, будет запутывать ошибки, если кто-то называет foo("bad", "argument", "next", 42)
. То, что я хочу написать, это что-то вроде:
void foo(const char* args...) {
for (const char* arg : args) {
// Real work
}
}
Какие-либо предложения?
Изменение: существует также опция void fn(std::initializer_list<const char *> args)
, но это делает вызов foo({"hello", "world"});
которого я хочу избежать.
Ответы
Ответ 1
Хотя все остальные ответы решают проблему, вы также можете сделать следующее:
namespace detail
{
void foo(std::initializer_list<const char*> strings);
}
template<typename... Types>
void foo(const Types... strings)
{
detail::foo({strings...});
}
Этот подход кажется (по крайней мере для меня) более читаемым, чем использование SFINAE и работает с С++ 11. Более того, он позволяет вам перемещать реализацию foo
в файл cpp
, что также может оказаться полезным.
Изменение: по крайней мере, с помощью GCC 8.1 мой подход, кажется, создает лучшее сообщение об ошибке при вызове с аргументами non const char*
:
foo("a", "b", 42, "c");
Эта реализация компилируется с помощью:
test.cpp: In instantiation of ‘void foo_1(const ArgTypes ...) [with ArgTypes = {const char*, int, const char*, const char*}]:
test.cpp:17:29: required from here
test.cpp:12:16: error: invalid conversion from ‘int to ‘const char* [-fpermissive]
detail::foo({strings...});
~~~~~~~~~~~^~~~~~~~~~~~~~
В то время как на основе SFINAE (реализация liliscent) производится:
test2.cpp: In function ‘int main():
test2.cpp:14:29: error: no matching function for call to ‘foo(const char [6], const char [6], int)
foo("hello", "world", 42);
^
test2.cpp:7:6: note: candidate: ‘template<class ... Args, typename std::enable_if<(is_same_v<const char*, Args> && ...), int>::type <anonymous> > void foo(Args ...)
void foo(Args... args ){
^~~
test2.cpp:7:6: note: template argument deduction/substitution failed:
test2.cpp:6:73: error: no type named ‘type in ‘struct std::enable_if<false, int>
std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>
Ответ 2
Я думаю, вы, вероятно, хотите что-то вроде этого:
template<class... Args,
std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>
void foo(Args... args ){
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
int main() {
foo("hello", "world");
}
Ответ 3
Примечание. Невозможно сопоставить только строковые литералы. Ближайшим вы можете подойти, чтобы сопоставить массив const char
.
Чтобы выполнить проверку типа, используйте шаблон функции, который принимает массивы const char
.
Для того, чтобы циклы над ними с диапазоном на основе for
, мы должны преобразовать его в initializer_list<const char*>
. Мы можем сделать это напрямую с фигурными скобками в заявлении, основанном for
диапазоне, потому что массивы будут распадаться на указатели.
Вот как выглядит шаблон функции (обратите внимание: это работает с нулевым или большим количеством строковых литералов. Если вы хотите один или несколько, замените подпись функции, чтобы принять хотя бы один параметр.):
template<size_t N>
using cstring_literal_type = const char (&)[N];
template<size_t... Ns>
void foo(cstring_literal_type<Ns>... args)
{
for (const char* arg : {args...})
{
// Real work
}
}
Ответ 4
+1 для решения C+ +1 7.
Для решения C+ +1 1 возможный способ заключается в создании признаков типа для создания "и" нескольких значений (что-то похожее на std::conjunction
которое, к сожалению, доступно только с C+ [CN00 ] 7... когда вы можете использовать фальцовку, и вам больше не нужна std::conjunction
conj (thanks liliscent)).
template <bool ... Bs>
struct multAnd;
template <>
struct multAnd<> : public std::true_type
{ };
template <bool ... Bs>
struct multAnd<true, Bs...> : public multAnd<Bs...>
{ };
template <bool ... Bs>
struct multAnd<false, Bs...> : public std::false_type
{ };
поэтому foo()
можно записать в виде
template <typename ... Args>
typename std::enable_if<
multAnd<std::is_same<char const *, Args>::value ...>::value>::type
foo (Args ... args )
{
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
Используя C+ +1 4, multAnd()
можно записать как функцию constexpr
template <bool ... Bs>
constexpr bool multAnd ()
{
using unused = bool[];
bool ret { true };
(void)unused { true, ret &= Bs ... };
return ret;
}
поэтому foo()
станет
template <typename ... Args>
std::enable_if_t<multAnd<std::is_same<char const *, Args>::value ...>()>
foo (Args ... args )
{
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
--- РЕДАКТИРОВАТЬ ---
Jarod42 (спасибо!) Предлагает гораздо лучший способ разработки multAnd
; что-то вроде
template <typename T, T ...>
struct int_sequence
{ };
template <bool ... Bs>
struct all_of : public std::is_same<int_sequence<bool, true, Bs...>,
int_sequence<bool, Bs..., true>>
{ };
Начиная с C+ +1 4 можно использовать std::integer_sequence
вместо имитации (int_sequence
).
Ответ 5
Используя 17-кратные выражения С++ в операторе запятой, вы можете просто сделать следующее:
#include <iostream>
#include <string>
#include <utility>
template<typename OneType>
void foo_(OneType&& one)
{
std::cout << one;
}
template<typename... ArgTypes>
void foo(ArgTypes&&... arguments)
{
(foo_(std::forward<ArgTypes>(arguments)), ...);
}
int main()
{
foo(42, 43., "Hello", std::string("Bla"));
}
Живой демо здесь. Примечание. Я использовал foo_
внутри шаблона, потому что я не мог беспокоиться о том, чтобы записать 4 перегрузки.
Если вы действительно действительно хотите ограничить это строковыми литералами, замените подпись функции, поскольку ответ Nevin предлагает:
#include <cstddef>
#include <iostream>
#include <string>
#include <utility>
template<std::size_t N>
using string_literal = const char(&)[N];
template<std::size_t N>
void foo(string_literal<N> literal)
{
std::cout << literal;
}
template<std::size_t... Ns>
void foo(string_literal<Ns>... arguments)
{
(foo(arguments), ...);
}
int main()
{
foo("Hello", "Bla", "haha");
}
Живой демо здесь.
Обратите внимание, что это очень близко к синтаксису С++ 11 для достижения того же самого. См. Например, этот мой вопрос.
Ответ 6
Наилучшим образом, ближайший вы можете перейти к функции, принимающей любое произвольное число const char*
но ничто иное не использует функцию-шаблон и пересылку:
void foo_impl(std::initializer_list<const char*> args)
{
...
}
template <class... ARGS>
auto foo(ARGS&&... args)
-> foo_impl({std::forward<ARGS>(args)...})
{
foo_impl({std::forward<ARGS>(args)...});
}
Тонкость заключается в разрешении нормальных неявных преобразований.
Ответ 7
#include<type_traits>
#include<iostream>
auto function = [](auto... cstrings) {
static_assert((std::is_same_v<decltype(cstrings), const char*> && ...));
for (const char* string: {cstrings...}) {
std::cout << string << std::endl;
}
};
int main(){
const char b[]= "b2";
const char* d = "d4";
function("a1", b, "c3", d);
//function(a, "b", "c",42); // ERROR
}
Ответ 8
А сейчас нечто соверешнно другое...
Вы можете написать структуру обертки типа следующим образом
template <typename, typename T>
struct wrp
{ using type = T; };
template <typename U, typename T>
using wrp_t = typename wrp<U, T>::type;
и функция foo()
получающая вариационный список char const *
просто становится
template <typename ... Args>
void foo (wrp_t<Args, char const *> ... args)
{
for ( char const * arg : {args...} )
std::cout << "- " << arg << std::endl;
}
Проблема в том, что вы не можете назвать это так, как хотите
foo("hello", "world");
потому что компилятор не может выводить типы Args...
Очевидно, вы можете указать список фиктивных типов
foo<void, void>("hello", "world");
но я понимаю, что это ужасное решение.
В любом случае, если вы соглашаетесь пройти через тривиальную функцию шаблона
template <typename ... Args>
void bar (Args ... args)
{ foo<Args...>(args...); }
ты можешь позвонить
bar("hello", "world");
Ниже приведен полный рабочий пример С++ 11
#include <iostream>
template <typename, typename T>
struct wrp
{ using type = T; };
template <typename U, typename T>
using wrp_t = typename wrp<U, T>::type;
template <typename ... Args>
void foo (wrp_t<Args, char const *> ... args)
{
for ( char const * arg : {args...} )
std::cout << "- " << arg << std::endl;
}
template <typename ... Args>
void bar (Args ... args)
{ foo<Args...>(args...); }
int main ()
{
bar("hello", "world"); // compile
// bar("hello", "world", 0); // compilation error
}
Ответ 9
Конечно, это возможно, это компилирует и запускает то, что вы хотите (обратите внимание)
#include<iostream>
template<class... Char>
// hehe, here is the secret
auto foo(const Char*... args ) ->decltype((char const*)(*std::begin({args...})), (char const*)(*std::end({args...})), void(0))
{
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
int main() {
foo("no", "sense","of","humor");
}
Это решение @liliscent, но с большим количеством сахара и, пожалуйста, @rubenvb, без enable_if
. Если вы считаете, что дополнительный код в качестве комментария (а это не так), обратите внимание, что вы увидите именно тот синтаксис, который вы ищете.
Обратите внимание, что вы можете подавать только однородный список вещей, которые конвертируются в char const*
, что было одной из ваших целей.