wake-up-neo.com

Typefreundliche Funktion

Ich möchte eine Funktion schreiben, die eine variable Anzahl von String-Literalen akzeptiert. Wenn ich in C schreibe, müsste ich Folgendes schreiben:

void foo(const char *first, ...);

und dann würde der Anruf aussehen:

foo( "hello", "world", (const char*)NULL );

Es scheint, dass es in C++ möglich sein sollte, bessere Ergebnisse zu erzielen. Das Beste, was ich mir ausgedacht habe, ist:

template <typename... Args>
void foo(const char* first, Args... args) {
    foo(first);
    foo(args);
}

void foo(const char* first) { /* Do actual work */ }

Genannt als:

foo("hello", "world");

Ich befürchte jedoch, dass die rekursive Natur und die Tatsache, dass wir keine Typenprüfung durchführen, bis wir zu einem einzigen Argument kommen, zu Fehlern führen kann, wenn jemand foo("bad", "argument", "next", 42) aufruft. Was ich will schreibe, ist so etwas wie:

void foo(const char* args...) {
    for (const char* arg : args) {
        // Real work
    }
}

Irgendwelche Vorschläge?

Edit: Es gibt auch die Option void fn(std::initializer_list<const char *> args), aber das macht den Aufruf foo({"hello", "world"});, den ich vermeiden möchte.

16
Martin Bonner

Während alle anderen Antworten das Problem lösen, können Sie auch Folgendes tun:

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

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

Dieser Ansatz scheint (zumindest für mich) lesbarer als die Verwendung von SFINAE und funktioniert mit C++ 11. Außerdem können Sie die Implementierung von foo in eine cpp-Datei verschieben, was ebenfalls nützlich sein kann.

Edit: Zumindest mit GCC 8.1 scheint mein Ansatz eine bessere Fehlermeldung zu erzeugen, wenn er mit Argumenten ohne const char* aufgerufen wird:

foo("a", "b", 42, "c");

Diese Implementierung wird kompiliert mit:

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

Während auf SFINAE (Implementierung von 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>
3
joe_chip

Ich glaube, du willst wahrscheinlich so etwas:

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

Hinweis: Es ist nicht möglich, nur String-Literale zu finden. Am nächsten kommen Sie mit einem const char-Array.

Verwenden Sie für die Typprüfung eine Funktionsvorlage, die const char-Arrays verwendet.

Um sie mit einer bereichsbasierten for zu durchlaufen, müssen wir sie in einen initializer_list<const char*> konvertieren. Dies kann direkt mit geschweiften Klammern in der Range-basierten for-Anweisung geschehen, da Arrays in Zeiger zerfallen.

Die Funktionsvorlage sieht folgendermaßen aus (Hinweis: Dies funktioniert bei null oder mehr String-Literalen. Wenn Sie einen oder mehrere Zeichenketten haben möchten, ändern Sie die Funktionssignatur so, dass mindestens ein Parameter verwendet wird.):

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
    }
}
8
Nevin

+1 für die C++ 17-Lösung von liliscent.

Für eine C++ 11-Lösung besteht die Möglichkeit, Typmerkmale zu erstellen, um ein "und" aus mehreren Werten zu erstellen (etwas ähnlich wie std::conjunction, das leider nur ab C++ 17 verfügbar ist ... wenn Sie es verwenden können falten und du brauchst std::conjunction nicht mehr (danke 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
 { };

so kann foo() als geschrieben werden

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

Mit C++ 14 kann multAnd() als constexpr-Funktion geschrieben werden

template <bool ... Bs>
constexpr bool multAnd ()
 {
   using unused = bool[];

   bool ret { true };

   (void)unused { true, ret &= Bs ... };

   return ret;
 }

so foo() werden

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

--- BEARBEITEN ---

Jarod42 (Danke!) Schlägt einen weitaus besseren Weg vor, eine multAnd zu entwickeln; etwas als

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>>
 { };

Ab C++ 14 kann std::integer_sequence anstelle der Imitation (int_sequence) verwendet werden.

2
max66

Mit C++ 17-fach Ausdrücken im Komma-Operator können Sie einfach Folgendes tun:

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

Live Demo hier . Hinweis Ich habe foo_ in der Vorlage verwendet, da ich nicht die Möglichkeit hatte, 4 Überladungen zu schreiben.


Wenn Sie dies wirklich wirklich auf Zeichenfolgenliterale beschränken möchten, ändern Sie die Funktionssignatur, wie Nevins Antwort vorschlägt:

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

Live Demo hier .

Beachten Sie, dass dies der C++ 11-Syntax sehr nahe kommt, um genau dasselbe zu erreichen. Siehe z. diese Frage von mir .

1
rubenvb

Die nächstgelegene kann eine Funktion erreichen, die eine beliebige Anzahl von const char* akzeptiert, aber nichts anderes verwendet eine Template-Funktion und Weiterleitung:

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

Die Subtilität besteht darin, die normalen impliziten Konvertierungen zuzulassen.

1
Deduplicator

Und jetzt etwas ganz anderes...

Sie können eine Typumbruchstruktur wie folgt schreiben

template <typename, typename T>
struct wrp
 { using type = T; };

template <typename U, typename T>
using wrp_t = typename wrp<U, T>::type;

und eine foo()-Funktion, die eine variadische Liste von char const * empfängt, wird einfach

template <typename ... Args>
void foo (wrp_t<Args, char const *> ... args)
 {
   for ( char const * arg : {args...} )
      std::cout << "- " << arg << std::endl;
 }

Das Problem ist, dass Sie es nicht so anrufen können, wie Sie möchten

foo("hello", "world");

weil der Compiler die Args...-Typen nicht ableiten kann.

Natürlich können Sie eine Liste von Dummy-Typen explizit angeben

 foo<void, void>("hello", "world");

aber ich verstehe, dass dies eine schreckliche Lösung ist.

Wie auch immer, wenn Sie sich damit einverstanden erklären, eine triviale Vorlagenfunktion zu durchlaufen

template <typename ... Args>
void bar (Args ... args)
 { foo<Args...>(args...); }

du kannst anrufen

bar("hello", "world");

Das folgende ist ein vollständiges Beispiel für 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
max66
#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
NoSenseEtAl