Search code examples
c++rxcpp

Empty angle brackets in C++


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.


Solution

  • 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