Variadic arguments in C++



  • Hallo,

    ist es mit Standard-C++-Mitteln (inklusive C++17) möglich, beliebig viele Argumente mit beliebigem Typ in eine Funktion zu packen, sodass man aber gezielt auf jedes einzelne Argument zugreifen kann?

    Ich kenne nur folgende Ansätze:

    template<typename T>
    decltype(auto) variadic_args(T args...)
    {
        va_list va;
        va_start(va, args);
    
        return va_arg(va, int);
    }
    

    Im C-Stil mit beliebig vielen Argumenten (hier hätte ich auch noch die Frage, was variadic_args(T args...) und variadic_args(T args, ...) unterscheidet).
    Und:

    template<typename... Args>
    auto first_temp(Args... args)
    {
        std::vector<std::any> res = {args...};
        return std::any_cast<int>(res[1]);
    }
    

    Nachteil bei beiden Codefragmenten ist, dass in std::any_cast als auch in va_arg immer der Typ des Parameters gebraucht wird. Ich will aber eigentlich, dass der Typ nicht in der Funktion auftaucht.
    Gibt es da irgendeine Möglichkeit?

    Danke! 🙂


  • Mod

    Bin mir gerade ziemlich sicher, dass es sich um einen Trollpost handelt...

    1. Variadic functions werden zur Laufzeit ihre Argumente vom Stack auslesen, abhängig von Alignment und Größe des Typs. Das bringt i.d.R. einen Overhead mit sich, weil solche Funktionen anderswie die Zahl und Typen ihrer Argumente bestimmen müssen. Ich erwarte, dass Compiler die va_arg Aufrufe zu einem Konstanten Offset verarbeiten, wenn es keine Branches gibt. Für den gewöhnlichen Anwendungsfall gibt es aber Branches, bspw. für Formatierungsfunktionen wie printf . (Da könnte der Compiler wiederum zur Compilezeit den Formatstring interpretieren.)

    2. Dein Einsatz von variadic templates ist kompletter Unsinn. Wenn wir das erste Argument zurückgeben wollten, ginge das so:

    template<typename Arg, typename... Args>
    decltype(auto) first_temp(Arg&& arg, Args&&... ) {
        return std::forward<Arg>(arg);
    }
    

    Die Argumente mittels type erasure in einen Container zu packen ist völlig bescheuert; die ganze Intention von variadic templates war, Laufzeitkosten zu sparen indem die Polymorphie zur Compilezeit gehandhabt wird.

    3. Wenn du nun auf jedes Element einzeln zugreifen möchtest, gäbe es einige Wege. Einer wäre ein entsprechendes Tupel:

    template <std::size_t I, typename... Ts>
    constexpr decltype(auto) nth(Ts&&... ts) noexcept {
      std::tuple<Ts&...> tup(ts...);
      return std::get<I>(tup);
    }
    

    Oder aber mittels des folgenden auxiliaren Tempaltes

    namespace detail {
      template <std::size_t>
      struct sink {
        template<typename... T>
        constexpr sink(T&&...) noexcept {}
      };
    
      template <std::size_t... I, typename T, typename... Ts>
      constexpr decltype(auto) nth(std::index_sequence<I...>, sink<I+0>..., T&& t, Ts&&... ) noexcept {
        return std::forward<T>(t);
      }
    }
    
    template <std::size_t I, typename... Ts>
    constexpr decltype(auto) nth(Ts&&... ts) noexcept {
      return detail::nth(std::make_index_sequence<I>{}, std::forward<Ts>(ts)...);
    }
    

    Hier schlägt GCC fehl und Clang hat einen ICE... also eher

    #include <tuple>
    #include <iostream>
    
    namespace detail {
      template <std::size_t>
      struct sink {
        template<typename... T>
        constexpr sink(T&&...) noexcept {}
      };
    
      template <std::size_t... I, typename T, typename... Ts>
      constexpr decltype(auto) nth_(sink<I>..., T&& t, Ts&&... ) noexcept {
        return std::forward<T>(t);
      }
    
      template <std::size_t... I, typename... Ts>
      constexpr decltype(auto) nth(std::index_sequence<I...>, Ts&&... ts ) noexcept {
        return nth_<I...>(std::forward<Ts>(ts)...);
      }
    }
    
    template <std::size_t I, typename... Ts>
    constexpr decltype(auto) nth(Ts&&... ts) noexcept {
      return detail::nth(std::make_index_sequence<I>{}, std::forward<Ts>(ts)...);
    }
    
    int main() {
      std::cout << nth<2>(5,6,1,2);
    }
    

    Demo.

    Falls der Index dynamisch ist, und die Typen unterschiedlich sein könnten, würde ich eher auf einen Visitor tippen. Aber all das ist völlig spekulativ, wenn du nicht den konkreten Anwendungsfall aufzeigst. Je nachdem eignen sich andere Dinge.


  • Mod

    varly schrieb:

    hier hätte ich auch noch die Frage, was variadic_args(T args...) und variadic_args(T args, ...) unterscheidet

    Nichts. Das Komma ist optional.



  • Arcoth schrieb:

    Bin mir gerade ziemlich sicher, dass es sich um einen Trollpost handelt...

    Nein, ist leider Unwissenheit.

    Danke für deine Erklärungen.
    Und auch danke camper.


Log in to reply