Вариантная вариационная функция

Я хочу написать функцию, которая принимает переменное число строковых литералов. Если бы я писал на 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*, что было одной из ваших целей.