3

Below is the printall function I would like to work. that is access what is stored in in_vars one by one. How could I do this?

PS: I know I can do so with recursion but I do not want to.

template <class... Ts>
void printall(Ts... in_vars)
{
    for (int x = 0; x< sizeof...(in_vars) ;x++)
        cout << in_var <<" ";
}

3 Answers3

2

You can only expand variadic packs, not iterate on them.

The solution is to expand the expression you have in your loop:

template <class... Ts>
void printall(Ts... in_vars)
{
    ((std::cout << in_vars << " "), ...);
}

Here I'm using the operator , to expand multiple expression and not use their return value.

If you can't use fold expressions, you can use a generic lambda and the expansion hack:

template <class... Ts>
void printall(Ts... in_vars)
{
    auto print = [](auto const& elem){ std::cout << elem << " "; };

    using expand_t = int[];
    (void) expand_t{(print(in_vars), 0)..., 0};
}

If you only have C++11, you can put the expression inside the expansion hack:

template <class... Ts>
void printall(Ts... in_vars)
{
    using expand_t = int[];
    (void) expand_t{(void(
        std::cout << in_vars << " "
    ), 0)..., 0};
}

The array expansion trick is explained here: Variadic template pack expansion

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • thank you very much. unfortunately, both any and fold are part of c++17, not c++ 11. – jojo_Aero_smith_the_dummy Nov 23 '21 at 11:27
  • Uh, better than mine (with lambda) – and suppressing the trailing space can be included similarly than I did (if relevant at all...): `std::string space; ((std::cout << space, space = ' ', std::cout << in_vars), ...);` – Aconcagua Nov 23 '21 at 11:29
  • @jojo_Aero_smith_the_dummy see my updated answer. Tell me if there's something not working for you still. – Guillaume Racicot Nov 23 '21 at 11:39
  • @GuillaumeRacicot thank you so much! it works perfectly! Thank you again for the explanation posted at end. I hope one day I will reach you guys' level of coding. – jojo_Aero_smith_the_dummy Nov 23 '21 at 13:26
  • 1
    @jojo_Aero_smith_the_dummy I reached that level by coding on my side and also hanging out here. Posting a slightly wrong answer and get tell by 3 experts what's wrong with it is a pretty good way to learn. Also solving problems that I wouldn't have encountered otherwise is quite good too. – Guillaume Racicot Nov 23 '21 at 13:29
0

Part 1

You have a very interesting question, thanks! I was always thinking same way - why standard C++ has no way of iterating typed pack Ts && ... args through regular dynamical loop.

Usually people write huge and ugly templated functions for each case of usage to iterate and expand arguments pack.

So inspired by your question I decided to implement just now a handy helper function that will allow dynamical access to elements of arguments pack inside regular dynamical loop.

At the bottom of my answer there is a full code that implements my idea. My helper function is called dget(idx, lambda, args...), wich means "dynamic get". Provided index idx and pack of arguments args... it applies lambda to argument at position idx.

My function dget() is impelemnted very efficiently through switch (idx). In most cases it will have zero overhead for getting element of pack if you compile with -O2 or -O3 option. It will be optimized away to same code as regular templated helper functions that is usually written for expansion of arguments pack.

In case if enough optimization is not possible, for example if loop can't be unrolled and/or if you get through using non-optimizable run time index (e.g. volatile) then still my code will be very efficient, because it is well known that all compilers optimize switch (idx) (in case of integer idx) as regular jump-table indexed by integer, so accessing idx-th element will have overhead of 1 or 2 runtime instructions to do actual jump.

One advanced example of usage of my dget() is inside to_str() function located in full code at the end of my answer, here below I provide central loop of this function, to give some explanations:

    std::string s;
    for (size_t i = 0; i < sizeof...(args); ++i)
        s += dget(i, [i](auto const & x){
            if constexpr(IS_TYPE(x, std::string))
                return std::to_string(i) +
                    ":str: '" + std::string(x) + "'";
            else if constexpr(IS_TYPE(x, int))
                return std::to_string(i) +
                    ":int: " + std::to_string(x);
            else if constexpr(IS_TYPE(x, bool))
                return std::to_string(i) +
                    ":bool: " + std::string(x ? "true" : "false");
            else {
                static_assert([]{ return false; }());
                return std::to_string(i) + std::string(":<bad_type>");
            }
        }, args...) + ", ";

this to_str() is just a Toy example, it might be implemented more efficiently and shorter and withoug dget() with using just std::stringstream, but I made it more complex just to show how to use specifically dget().

Basically this loop above naturally in run time iterates through all elements of provided args... pack and depending on type of argument converts it to string (with prefix) in a way specific for each type. You can see that I have regular run-time access to i-th element of args pack by using Visitor pattern with lambda.

One may argue that example block of code above can be implemented without loop, as following:

    std::string s;
    ([&](auto const & x){
        if constexpr(IS_TYPE(x, std::string))
            s += "str: '" + std::string(x) + "', ";
        else if constexpr(IS_TYPE(x, int))
            s += "int: " + std::to_string(x) + ", ";
        else if constexpr(IS_TYPE(x, bool))
            s += "bool: " + std::string(x ? "true" : "false") + ", ";
        else {
            static_assert([]{ return false; }());
            s += std::string("<bad_type>") + ", ";
        }
    }(args), ...);

but in this case you have two drawbacks: 1) my dget() can return a value that can be naturally used in natural loop logic, like I used in first code snippet. 2) in the last example we don't have i variable any more so we can't access or know index of the element, it may be a blocker for using non-loop variant. Anyway loop to me looks much more natural.

My dget() is very similar to std::visit that is used to access std::variant. Also I could implement my dget() through variant+visit, but I didn't want to have any overhead that has conversion to dynamical variant and back again. Of course good compiler will probably optimize away conversion to std::variant and back, but yet I decided to use switch (idx) for my implementation. Although std::variant conversion is not a bad choice as well. Maybe later I update my answer to include Part 2 where I do same stuff using std::variant instead of switch, and measure theirs performance difference.

Your original printall() function can also implemented easily and naturally through loop using my dget():

template <typename ... Ts>
void print_all(Ts && ... args) {
    for (size_t i = 0; i < sizeof...(args); ++i)
        dget(i, [](auto const & x){
            std::cout << std::boolalpha << x << " ";
        }, args...);
}

although solution of @GuillaumeRacicot is shorter and more canonical and better to use for this specific case of std::cout.

So full code below. Click Try it online! to see my code in action on Online servers. Also don't forget to see console Output located after the code. Also you may try to come back to my answer later in case if I decide to extend it by writing Part 2 where I plan to use std::variant for implementing same dget().

Try it online!

#include <cstdint>
#include <tuple>
#include <stdexcept>
#include <iostream>
#include <iomanip>

using std::size_t;

template <typename F, typename ... Args>
inline decltype(auto) dget(size_t idx, F && f, Args && ... args) {
    #define C(i) \
        case (i): { \
            if constexpr(i < std::tuple_size_v<std::tuple<Args...>>) \
                return f(std::get<i>(std::tie(std::forward<Args>(args)...))); \
            else goto out_of_range; \
        }

    static_assert(sizeof...(Args) <= 30);

    switch (idx) {
        C( 0) C( 1) C( 2) C( 3) C( 4) C( 5) C( 6) C( 7) C( 8) C( 9)
        C(10) C(11) C(12) C(13) C(14) C(15) C(16) C(17) C(18) C(19)
        C(20) C(21) C(22) C(23) C(24) C(25) C(26) C(27) C(28) C(29)

        default:
            goto out_of_range;
    }

    #undef C

    out_of_range:
        throw std::runtime_error("dget: out of range!");
}

template <typename ... Ts>
void print_all(Ts && ... args) {
    for (size_t i = 0; i < sizeof...(args); ++i)
        dget(i, [](auto const & x){
            std::cout << std::boolalpha << x << " ";
        }, args...);
    std::cout << std::endl;
}

template <typename ... Ts>
std::string to_str(Ts && ... args) {
    #define IS_TYPE(x, t) (std::is_same_v<std::decay_t<decltype(x)>, t>)

    std::string s;
    for (size_t i = 0; i < sizeof...(args); ++i)
        s += dget(i, [i](auto const & x){
            if constexpr(IS_TYPE(x, std::string))
                return std::to_string(i) +
                    ":str: '" + std::string(x) + "'";
            else if constexpr(IS_TYPE(x, int))
                return std::to_string(i) +
                    ":int: " + std::to_string(x);
            else if constexpr(IS_TYPE(x, bool))
                return std::to_string(i) +
                    ":bool: " + std::string(x ? "true" : "false");
            else {
                static_assert([]{ return false; }());
                return std::to_string(i) + std::string(":<bad_type>");
            }
        }, args...) + ", ";

    #undef IS_TYPE

    return s;
}

int main() {
    print_all(123, "abc", true);
    std::cout << to_str(123, std::string("abc"), true) << std::endl;
}

Output:

123 abc true 
0:int: 123, 1:str: 'abc', 2:bool: true, 

Part 2

Just out of curiosity I decided to implement second variant of dget() implementation, described in Part 1.

This variant uses std::array table prefilled with std::function objects providing access to elements of args pack.

Initially I was planning to implement Part 2 of my answer using std::variant, but figured out that variant is not simplifying anything, while having more overhead. Hence I decided to use std::array+std::function table.

This Part 2 implementation of dget() may or may not be slower than Part 1 implementation, depending on how compiler optimizes the code. I didn't do any speed measurement, maybe in future I'll do them and update my answer. Small Part 2 overhead may be due to static guard of table, which needs every time to check if static constant was already initialized, although it will always generate successful CPU's branch prediction. Also Part 2 might be slower due to usage of std::function which may wrap initial lambdas into virtual pointer dereferenced call.

Without extra explanation providing full code below, it has only other body of dget() compared to code of Part 1. Test examples and console output is same.

Try it online!

#include <cstdint>
#include <tuple>
#include <stdexcept>
#include <iostream>
#include <iomanip>
#include <variant>
#include <functional>
#include <array>

using std::size_t;

template <typename F, typename ... Args>
inline decltype(auto) dget(size_t idx, F && f, Args && ... args) {
    using RetT = decltype(f(std::get<0>(std::tie(std::forward<Args>(args)...))));
    using FfT = decltype(std::forward<F>(f));
    #define C(i) \
        [](FfT f, decltype(std::forward<Args>(args)) ... argsi) { \
            if constexpr(i < sizeof...(Args)) \
                return f(std::get<i>(std::tie(std::forward<Args>(argsi)...))); \
            else { \
                throw std::runtime_error( \
                    "dget: programming logic, table access out of range"); \
                return f(std::get<0>(std::tie(std::forward<Args>(argsi)...))); \
            } \
        },

    static std::array<std::function<RetT(
        FfT, decltype(std::forward<Args>(args))...
    )>, 30> const tab = {
        C( 0) C( 1) C( 2) C( 3) C( 4) C( 5) C( 6) C( 7) C( 8) C( 9)
        C(10) C(11) C(12) C(13) C(14) C(15) C(16) C(17) C(18) C(19)
        C(20) C(21) C(22) C(23) C(24) C(25) C(26) C(27) C(28) C(29)
    };

    #undef C

    static_assert(sizeof...(Args) <= std::tuple_size_v<decltype(tab)>);

    if (idx >= sizeof...(Args))
        throw std::runtime_error("dget: index out of range!");

    return tab[idx](std::forward<F>(f), std::forward<Args>(args)...);
}

template <typename ... Ts>
void print_all(Ts && ... args) {
    for (size_t i = 0; i < sizeof...(args); ++i)
        dget(i, [](auto const & x){
            std::cout << std::boolalpha << x << " ";
        }, args...);
    std::cout << std::endl;
}

template <typename ... Ts>
std::string to_str(Ts && ... args) {
    #define IS_TYPE(x, t) (std::is_same_v<std::decay_t<decltype(x)>, t>)

    std::string s;
    for (size_t i = 0; i < sizeof...(args); ++i)
        s += dget(i, [i](auto const & x){
            if constexpr(IS_TYPE(x, std::string))
                return std::to_string(i) +
                    ":str: '" + std::string(x) + "'";
            else if constexpr(IS_TYPE(x, int))
                return std::to_string(i) +
                    ":int: " + std::to_string(x);
            else if constexpr(IS_TYPE(x, bool))
                return std::to_string(i) +
                    ":bool: " + std::string(x ? "true" : "false");
            else {
                static_assert([]{ return false; }());
                return std::to_string(i) + std::string(":<bad_type>");
            }
        }, args...) + ", ";

    #undef IS_TYPE

    return s;
}

int main() {
    print_all(123, "abc", true);
    std::cout << to_str(123, std::string("abc"), true) << std::endl;
}
Arty
  • 14,883
  • 6
  • 36
  • 69
0

Yes, you can index it with numbers if you insist but with a small detour:

#include <array>

template <class... Ts>
void printall(Ts... in_vars) {
    for (std::size_t x = 0; x < sizeof...(in_vars); ++x)
        std::cout << std::array{in_vars...}[x] << " ";
}

The compiler will likely optimise the detour out. I'd still not recommend this in this particular situation where you want to access all. But if you want to access a specific one, std::array is your friend.

Note however, that if the type of all Ts... has no common type the array initialisation will fail.


Edit: This also works with C++11, there access becomes:

std::array<typename std::common_type<Ts...>::type,sizeof...(in_vars)>{in_vars...}[x]
bitmask
  • 32,434
  • 14
  • 99
  • 159