Типизированная безопасная вариадическая функция - PullRequest
0 голосов
/ 15 мая 2018

Я хочу написать функцию, которая принимает переменное число строковых литералов. Если бы я писал на 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"});, которого я хочу избежать.

Ответы [ 9 ]

0 голосов
/ 16 мая 2018

Ну, самое близкое, что вы можете получить к функции, принимающей любое произвольное число 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)...});
}

Хитрость заключается в том, чтобы разрешить обычные неявные преобразования.

0 голосов
/ 16 мая 2018

А теперь ... для чего-то совершенно другого ...

Вы можете написать структуру типа оболочки следующим образом

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");

Ниже приведен полный рабочий пример C ++ 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
 }
0 голосов
/ 15 мая 2018

Хотя все другие ответы решают проблему, вы также можете сделать следующее:

namespace detail
{
    void foo(std::initializer_list<const char*> strings);
}

template<typename... Types>
void foo(const Types... strings)
{
    detail::foo({strings...});
}

Этот подход кажется (по крайней мере, мне) более читабельным, чем использование SFINAE и работает с C ++ 11,Более того, он позволяет вам перенести реализацию foo в файл cpp, что также может быть полезно.

Редактировать: по крайней мере, в GCC 8.1 мой подход, по-видимому, дает лучшее сообщение об ошибке при вызове с помощьюне 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>
0 голосов
/ 16 мая 2018

Конечно, возможно, это компилирует и запускает то, что вы хотите (обратите внимание)

#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*, что, по-видимому, было одной из ваших целей.

0 голосов
/ 15 мая 2018
#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
}
0 голосов
/ 15 мая 2018

Используя C ++ 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 перегрузки.


Если вы действительно действительно хотите ограничить это строковыми литералами, измените сигнатуру функции, как подсказывает ответ Невина:

#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");
}

Демонстрация здесь .

Обратите внимание, что это очень близко к синтаксису C ++ 11 для достижения того же результата. Смотрите, например, мой вопрос .

0 голосов
/ 15 мая 2018

Примечание: невозможно сопоставить только строковые литералы.Самое близкое, что вы можете найти, - это сопоставить массив const char.

Чтобы выполнить проверку типа, используйте шаблон функции, который принимает const char массивы.

Чтобы зациклить их с помощью range-на основе 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
    }
}
0 голосов
/ 15 мая 2018

+ 1 для решения C ++ 17 liliscent.

Для решения C ++ 11 возможный способ - создать черты типа для создания значений "и" из нескольких значений (что-то похожее на std::conjunction, который, к сожалению, доступен только начиная с C ++ 17 ... когда вы можете использовать свертывание и вам больше не нужно std::conjunction (спасибо, лилис)).

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 ++ 14, 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";
    }
 }

--- EDIT ---

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 ++ 14 можно использовать std::integer_sequence вместо его имитации (int_sequence).

0 голосов
/ 15 мая 2018

Я думаю, вы, вероятно, хотите что-то вроде этого:

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");
}
...