3
\$\begingroup\$

Suppose we are given a C++ vector. We want to specify a variable amount of indices and select elements from a vector being indexed. I have two implementation: (A) one relies on C++11 initializer lists, and the second one (B) on va_list and macro mess. Arrangement B is however funkier to type because it requires no typing the braces comprising the initializer list.

So my main question is: which one should an adult C++ programmer use, if any?

coderodde.h:

#ifndef CODERODDE_H
#define CODERODDE_H

#include <cstdarg>
#include <iostream>
#include <vector>

#define NUM_ARGS(...) (sizeof((size_t[]){__VA_ARGS__}) / sizeof(size_t))

namespace coderodde {

    template<class T> 
    std::vector<T> select(const std::vector<T>& vec, 
                          const std::vector<size_t>& indices) 
    {
        std::vector<T> ret;

        for (auto index : indices) {
            ret.push_back(vec.at(index));
        }

        return ret;
    }

    template<class T>
    std::vector<T> select(const std::vector<T>& vec, const size_t len, ...)
    {
        std::va_list ap;
        std::vector<T> ret;
        va_start(ap, len);

        for (size_t i = 0; i < len; ++i) 
        {
            ret.push_back(vec.at(va_arg(ap, size_t)));
        }

        va_end(ap);
        return ret;
    }
}

#define SELECT(vec, ...) (select(vec, NUM_ARGS(__VA_ARGS__), __VA_ARGS__))

#endif // CODERODDE_H

The test driver main.cpp:

#include <iostream>
#include <string>
#include <vector>
#include "coderodde.h"

using std::cout;
using std::endl;
using std::string;
using std::vector;
using coderodde::select;

void test_smart(const vector<string>& input) 
{
    const size_t N = input.size();

    for (size_t ca = 0; ca < N; ++ca) 
    {
        for (size_t cb = 0; cb < N; ++cb) 
        {
            for (size_t cc = 0; cc < N; ++cc) 
            {
                for (auto s : select(input, { ca, cb, cc }))
                {
                    cout << s;
                }

                cout << endl;
            }
        }
    }

}

void test_stupid(const vector<string>& input) 
{
    const size_t N = input.size();

    for (size_t ca = 0; ca < N; ++ca) 
    {
        for (size_t cb = 0; cb < N; ++cb) 
        {
            for (size_t cc = 0; cc < N; ++cc) 
            {
                for (auto s : SELECT(input, ca, cb, cc))
                {
                    cout << s;
                }

                cout << endl;
            }
        }
    }
}

int main(int argc, char** argv)
{
    std::vector<std::string> string_vec {"A", "B", "C", "D"};
    cout << "Smart:" << endl;
    test_smart(string_vec);
    cout << "Stupid:" << endl;
    test_stupid(string_vec);
    return 0;
}
\$\endgroup\$
4
\$\begingroup\$

You can avoid problems related to variadic macros and problems related to C-style variadic functions altogether by creating a function that takes an std::initializer_list directly:

template<class T>
std::vector<T> select(const std::vector<T>& vec, std::initializer_list<std::size_t> indices)
{
    std::vector<T> ret;
    for (std::size_t ind: indices) 
    {
        ret.push_back(vec.at(ind));
    }
    return ret;
}

Moreover, it is the easiest to read of the three implementations.

\$\endgroup\$
  • \$\begingroup\$ That's a nice trick. Do you have a rule of thumb to help decide when to use the initializer list over variadic functions or is it just too situational to make a generalization. \$\endgroup\$ – Martin York Mar 7 '15 at 2:00
  • \$\begingroup\$ @LokiAstari Initializer lists are nice when you only have several elements for only one type while variadic constructors are better when you have parameters of several types. At least initializer lists help you to make sure that you don't have unknown types. But I don't know of any rule of thumb. Generally speaking, when both can be used, it's up to the programmer to decide. \$\endgroup\$ – Morwenn Mar 7 '15 at 11:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.