These articles are written by Codalogic empowerees as a way of sharing knowledge with the programming community. They do not necessarily reflect the opinions of Codalogic.

An all_true() Function using Variadic Templates

By: Pete, October 2020

A colleague wanted to test that all his initialisation functions were successful before executing some additional code.

An additional constraint was that all the initialisation functions had to be run even if one of the earlier init functions failed.

This latter requirement meant that short circuit code like the following wasn't the solution:

if( init1() && init2() && init3() )
    run_system();

One slution is to do:

bool res1 = init1();
bool res2 = init2();
bool res3 = init3();
if( res1 && res2 && res3 )
    run_system();

However, this not ideal from a maintenance perspective if you want to add or remove inits() as the system evolves.

One way to tackle this is to use variadic templates.

We can create a template function that looks something like this:

template< typename Tfirst, typename ...Targs >
bool all_true( Tfirst first, Targs... args )
{
    return first && all_true( args... );
}

The function peels off the first parameter and then calls the template function again with one less parameter.

We then need a function to handle the case where there are no parameters left:

bool all_true()
{
    return true;
}

This is a very functional style of coding.

It can be called with code like:

all_true( init1(), init2(), init3() );

One possible concern in the above template function is that it does not insist on the parameter passed to it being a bool. For example, lines like the following will be compiled:

std::cout << all_true( succeeds("a"), 11, succeeds("c") ) << "\n";

This can be addressed by modifying the template by adding a std::enable_if term as below (note I have formatted the std::enable_if line so that it can be easily commented out so you can experiment):

template< typename Tfirst, typename ...Targs
    , typename = std::enable_if_t<std::is_same_v<Tfirst,bool>>
    >
bool all_true( Tfirst first, Targs... args )
{
    return first && all_true( args... );
}

The complete code looks as below and is available at: https://godbolt.org/z/7dvc6jWx7

#include <iostream>

bool all_true()
{
    return true;
}

/* Could be added for optimisation
bool all_true( bool f )
{
    return f;
}
*/

template< typename Tfirst, typename ...Targs
    , typename = std::enable_if_t<std::is_same_v<Tfirst,bool>>
    >
bool all_true( Tfirst first, Targs... args )
{
    return first && all_true( args... );
}

bool succeeds( const char * s ) { std::cout << "S(" << s << ") "; return true; }
bool fails( const char * s ) { std::cout << "F(" << s << ") "; return false; }

int main()
{
    std::cout << all_true( succeeds("a"), succeeds("b"), fails("c") ) << "\n";
    std::cout << all_true( succeeds("a"), fails("b"), succeeds("c") ) << "\n";
    std::cout << all_true( succeeds("a"), succeeds("b"), succeeds("c") ) << "\n";
    std::cout << all_true() << "\n";

    // These fail to compile with std::enable_if_t<> present but compiles otherwise
    //std::cout << all_true( 11, succeeds("b"), succeeds("c") ) << "\n";
    //std::cout << all_true( succeeds("a"), 11, succeeds("c") ) << "\n";
}
F(c) S(b) S(a) 0
S(c) F(b) S(a) 0
S(c) S(b) S(a) 1
1

Note that one thing that might bite you is that the succeeds() and fails() functions are not evaluated in the obvious order (C&C++ push parameters on the stack from right to left and even within that constraint the compiler is allowed to evaluate the functions in any order it likes).

The above peeling technique is a nice and common solution to such problems but as of C++17 variadic template fold expressions offer an even more compact solution. Our template function becomes:

template<typename ...Args>
bool all_true( Args... args )
{
    return (... && args);
}

And the full code example becomes (available at: https://godbolt.org/z/evnodT4vj):

#include <iostream>

template<typename ...Args >
bool all_true( Args... args )
{
    return (... && args);
}

bool succeeds( const char * s ) { std::cout << "S(" << s << ") "; return true; }
bool fails( const char * s ) { std::cout << "F(" << s << ") "; return false; }

int main()
{
    std::cout << all_true( succeeds("a"), succeeds("b"), fails("c") ) << "\n";
    std::cout << all_true( succeeds("a"), fails("b"), succeeds("c") ) << "\n";
    std::cout << all_true( succeeds("a"), succeeds("b"), succeeds("c") ) << "\n";
    std::cout << all_true() << "\n";

    // These compile without the std::enable_if_t<> present
    std::cout << all_true( 11, succeeds("b"), succeeds("c") ) << "\n";
    std::cout << all_true( succeeds("a"), 11, succeeds("c") ) << "\n";
}

With the results being as before:

F(c) S(b) S(a) 0
S(c) F(b) S(a) 0
S(c) S(b) S(a) 1
1
S(c) S(b) 1
S(c) S(a) 1

Note however that I haven't been able to work out how to ensure that the function only accepts bool values.

Keywords