0

There are some cases in variadic templates which I do not understand why they don't work. Say e.g. I have a template type that stores several characters:

template<char...chars> struct Test{
};

// If I try to print it with parameter packs everything okay.
template<char...chars> 
constexpr std::ostream& toOstream( std::ostream & os , Test <chars...> ) { 
    return ( os << ... << chars ) ;
};

But I do not understand why the recursive version does not work (this is simmilar to https://stackoverflow.com/users/1365260/example 's version in How to write a variadic template recursive function?):

// Case A
template<char c=0, char...chars> 
constexpr std::ostream& toOstream2( std::ostream & os , Test <c,chars...> ) { 
    return sizeof...(chars) == 0 ? ( os << c ) :
        toOstream2( os << c, Test<chars...>() ) ;
};

// Case B: Nor I understand why can't I use it inside another class:
template< class Base >
struct Deriv{
    static constexpr std::ostream& toOstream( std::ostream & os ) { 
        return ( os << Base() );
    };
};

// main program:
int main(void){
    toOstream( cout, Test<'k','a','b'>() ) << endl;
    //toOstream2( cout, Test<'k','a','b'>() ) << endl;  // this gives error. Case A
    //Deriv< Test<'k','a','b'> >().toOstream( cout ) << endl;  // this gives error. Case B
    return 1;
}

The output of the program is:

kab

The error if I uncomment for Case A is:

xxxxxxxxxxx: In instantiation of ‘constexpr std::ostream& toOstream2(std::ostream&, Test<c, chars ...>) [with char c = 'b'; char ...chars = {}; std::ostream = std::basic_ostream<char>]’:
xxxxxxxxxxx:   recursively required from ‘constexpr std::ostream& toOstream2(std::ostream&, Test<c, chars ...>) [with char c = 'a'; char ...chars = {'b'}; std::ostream = std::basic_ostream<char>]’
...etc

xxxxxxxxxxxxxxxx: note:   candidate expects 2 arguments, 0 provided
      |         toOstream2( os << c, Test<chars...>() ) ;
      |         ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

And for Case B:

error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘Test<'k', 'a', 'b'>’)
   60 |         return ( os << Base() );
      |                ~~~~~^~~~~~~~~~~

I'm using g++ 10.2.1 20210110

Any hint?

Edit: After looking at the answers, I wonder why this compiles without problems and my Case A doesn't

template<int First=0, int... Rest>
constexpr int fauo()
{
    return sizeof...(Rest)==0 ? First : First + fauo<Rest...>();
}
#include<iostream>
int main(void){
    std::cout << fauo<0,9>() << std::endl;
    return 0;
}

Is there something special for functions that does not work for classes?

ctejeda
  • 1
  • 2
  • In case A, the issue is that the branches are run-time branches (i.e. in a ternary operator, both arguments *must* compile, regardless of whether they're called.) You need to use a constexpr-if to avoid compiling the untaken branch. In case B, I'm not sure why you expect `return ( os << Base() );` to work? `std::cout` doesn't know how to stream a `Test`. Don't you mean something like `return toOstream2( os, Base() );`? – cigien Aug 15 '22 at 00:14
  • @cigien thank you! in case A it was just that, in case B, I wanted ::toOstream( os, Base() ). With that, it worked. – ctejeda Aug 15 '22 at 21:03

2 Answers2

1

To get this to work, you need to resolve the sizeof...(char) at compile time. You could use constexpr-if:

template <char c, char... chars>
constexpr std::ostream& toOstream2(std::ostream& os, Test<c, chars...>) {
    if constexpr (sizeof...(chars) == 0) return os << c;
    else return toOstream2(os << c, Test<chars...>());
};

Or pre C++17, using overloads:

template <char c>
constexpr std::ostream& toOstream2(std::ostream& os, Test<c>) {
    return os << c;
};

template <char c, char... chars>
constexpr std::ostream& toOstream2(std::ostream& os, Test<c, chars...>) {
    return toOstream2(os << c, Test<chars...>());
};

For case B, you'd need to call toOstream2 with a Base instance instead of trying to do os << Base() since you haven't defined operator<< for Base.

template <class Base>
struct Deriv {
    static constexpr std::ostream& toOstream(std::ostream& os) {
        return toOstream2(os, Base{});
    };
};

With added operator<< overloads, your case B could look like this:

#include <iostream>

template <char... chars> struct Test {};

template <char c, char... chars>
std::ostream& operator<<(std::ostream& os, Test<c, chars...>) {
    if constexpr (sizeof...(chars) == 0) return os << c;
    else return os << c << Test<chars...>{};
};

template <class Base>
struct Deriv {
    friend std::ostream& operator<<(std::ostream& os, const Deriv<Base>&) {
        return os << Base{};
    };
};

int main() {
    std::cout << Test<'k','a','b'>() << '\n';        // prints "kab"
    std::cout << Deriv<Test<'k','a','b'>>() << '\n'; // prints "kab"
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
0

The first (Case A) doesn't work because you expect at minimum one argument to the template. This is even so if you specify a default argument. To make it work, simply allow a 0-character overload before the definition of toOstream2() as:

constexpr std::ostream& toOstream2( std::ostream & os , Test <> ) { return os; }

The second (Case B) doesn't work because you haven't called toOstream2(), but expect operator<< to work. If you want that, you need to name your function as operator<<, not toOstream2().

lorro
  • 10,687
  • 23
  • 36