Search code examples
c++algorithmtemplatesc++17variadic-templates

Attempt at mimicking the python print function using variadic templates not working


I came across variadic templates while reading a book and thought it would be pretty cool to implement a python style print function.

Here is the code.

#include <iostream>
#include <string>

#define None " "

template<typename T, typename... Tail>
void print(T head, Tail... tail, const char* end = "\n")
{
    std::cout << head << end;
    if constexpr (sizeof...(tail) > 0)
    {
        print(tail..., end);
    }
}

int main()
{
    // Error: no instance of function template "print" matches the argument list
    print("apple", 0, 0.0f, 'c', true);

    // Error: no instance of function template "print" matches the argument list
    print("apple", 0, 0.0f, 'c', true, None);
}

Expected result from those two function calls:

First:    Second:
apple     apple 0 0.0f 'c' 1
0
0.0f
'c'
1

Removing const char* end = "\n" from the function signature gets the code to compile, but I want that functionality of specifying the last parameter to state whether to print a newline.

Is this possible at all?


Solution

  • It is possible, but not in the way you have tried.

    You could do something like the following (one possible solution in ):

    • Provide an enum (Ending) which will be used to specify the way of printing (i.e newline, with space, etc).
    • Split function print and one of them will be used to print one argument at a time, where we will check for the way of printing;
    • The other print will be used to call the variadic arguments by the caller. Here we're using fold expressions to call the first print overload.

    Something like: (Live Demo)

    enum struct Ending {  NewLine = 0, Space };
    
    template<Ending end, typename Type>
    void print(const Type& arg) noexcept
    {
        if constexpr (end == Ending::NewLine)  {
            std::cout << arg << '\n';
        }
        else if constexpr (end == Ending::Space) {
            std::cout << arg << ' ';
        }
    }
    
    template <Ending end, typename... Args>
    void print(const Args& ... args) noexcept
    {
        (print<end>(args), ...);
    }
    

    Now you can specify, how to end the line

    print<Ending::NewLine>("apple", 0, 0.0f, 'c', true);
    print<Ending::Space>("apple", 0, 0.0f, 'c', true);
    

    "You don't like the overload!?" Then we can make it inside a single print function with help of an immediately invoking lambda function.

    (Live Demo)

    template <Ending end, typename... Args>
    void print(const Args& ... args) noexcept
    {
        ([] (Ending, const auto& arg) noexcept {
            if constexpr (end == Ending::NewLine) {
                std::cout << arg << '\n';
            }
            else if constexpr (end == Ending::Space) {
                std::cout << arg << ' ';
            }
        }(end, args), ...);
        //^^^^^^^^^^  ^^^^  ---> Invoke the lambda & expand the fold
    }