Empty angle brackets in C++
Asked Answered
C

1

5

When exploring RxCpp library I encountered the following sample which I cannot interpret.

    auto ints = rxcpp::observable<>::create(
        [](rxcpp::subscriber<int> s){
            s.on_next(1);
            s.on_next(2);
            s.on_completed();
    });

There are two declarations of observable class in the library:

template<class T, class SourceOperator>
class observable
    : public observable_base<T>
{
// ...
};

template<>
class observable<void, void>
{
// ...
};

What I was not able to comprehend is how the compiler manages to accept rxcpp::observable<>. piece. There could have been many explicit specializations of observable for different types, other than void,void.

The question is how the compiler interprets empty angle brackets in this code: rxcpp::observable<>.

I see no default template parameters in observable class, neither variadic template parameters which could explain this.

Then I thought it was somehow related to explicit template specialization, and tried to reproduce it in an isolated program, for instance like this

namespace isolated {
  template<class T>
  class Test {
  public:
    static void say() {
      cout << "I am generic" << endl;
    }
  };

  template<>
  class Test<int> {
  public:
    static void say() {
      cout << "I am integer" << endl;
    }
  };
}

int main() {
  isolated::Test<>::say(); // ERROR: too few arguments for class template.....
}

However it does not compile even though there is only one explicit specialization.

Connacht answered 10/3, 2020 at 20:54 Comment(3)
Looks like your missing something. The only way rxcpp::observable<> can work is if there are default template arguments. Look in the source code and see if there is a forward declaration of observable like template<class T = something, class SourceOperator = something_else> class observableColum
@JerryJeremiah He's talking about at the calling site (snippet 1), not the specialization in snippet 2.Melina
look for a declaration of the class template. It can have default template arguments. E.g. template<class T = void, class SourceOperator = void> class observable;Netta
C
6

What you are missing is

template<
     class T = void,
     class SourceObservable = typename std::conditional<std::is_same<T, void>::value,
         void, dynamic_observable<T>>::type>
 class observable;

from lines 142-146 of rx-predef.hpp

This forward declaration supplies default template arguments for the observable class and allows you to write observable<> which will use those default values. In your example that would be accomplished by adding

template<class T = int>
class Test;

Which gives you

namespace isolated {
  template<class T = int>
  class Test;

  template<class T>
  class Test {
  public:
    static void say() {
      cout << "I am generic" << endl;
    }
  };

  template<>
  class Test<int> {
  public:
    static void say() {
      cout << "I am integer" << endl;
    }
  };
}

int main() {
  isolated::Test<>::say(); // ERROR: too few arguments for class template.....
}

and outputs

I am integer

in this live example

Colum answered 10/3, 2020 at 21:20 Comment(2)
I wonder why someone would want to place forward declarations with template parameter defaults in a separate header, which is not supposed to be included directly by the library user.Connacht
@Connacht There seems to be quite a bit of dependency between the header files. Most likely that was the spot they "had to use" with their design in order for the defaults to get pulled into everywhere correctly.Colum

© 2022 - 2024 — McMap. All rights reserved.