Search code examples
c++inheritancetemplate-specialization

How to not having to specify the return type of a factory


I want consumers to use a Factory for the construction of special derivations of a Scheme.

Mandatorily, when asking a method from the factory, it returns a Table specialization. A method implementation comprises of two things:

  1. a Table specialization instance returned by Factory
  2. a Scheme specialization that is complient with the Table specialization type.

The point of this whole question is convenience, in that I do not want to force consumers at knowing what scheme is compliant to which table, not even to mention what table type a given factory method returns. And lastly, I still want advanced users to still be able to force-specify the Scheme specialization to be used (that's why factory only returns a table and not a full method; in that vain, I do not want to employ arguments in any factory routines).

Because I cannot use dynamic memory, I employ a template Scheme_handle class that can hold an instance of a Scheme specialization. But this defies its own purpose when the consumer is still demanded to know the necessary template type.

Below is my attempt.

#include<iostream>
//#include<memory> // infeasible since dynamic allocation

struct Table{ double data; };
struct Scheme{};
//
struct Table_A: Table{};
struct Table_B: Table{}; // there are too many different scheme types for use of std::conditional_t
//
struct Scheme_A: Scheme{
    Scheme_A(Table_A const& t){}
};
struct Scheme_B: Scheme{
    Scheme_B(Table_A const& t){}
    Scheme_B(Table_B const& t){}
};

struct Factory{
    /* factory implementation ... */
    static Table_A make_x(){ return Table_A{}; } // it is mandatory that Factory returns Tables
    static Table_B make_y(){ return Table_B{}; } // otherwise, Table would not have been
    static Table_A make_z(){ return Table_A{}; } // included in this minimum code example
};

// specializations still need a declaration in 2023
template<typename TableType> class Scheme_handle;

// the following two lines of terrible boilerplate are necessary 
// because c++ wont allow template<> using Scheme_handle<Table_A> = Scheme_A;
// same goes for typedef
template<> class Scheme_handle<Table_A>: public Scheme_A{ public: Scheme_handle(Table_A const& t):Scheme_A(t){} }; // Table_A -> Scheme_A
template<> class Scheme_handle<Table_B>: public Scheme_B{ public: Scheme_handle(Table_B const& t):Scheme_B(t){} }; // Table_B -> Scheme_B


// here is the consumer code that I need to be handable
struct MyClass{
    //
    // what more needs the compiler so one can drop the "<Table_A>" ?
    Scheme_handle<Table_A>   x1 = Factory::make_x(); // at least this gives me a handle to a scheme
    //
    Scheme_handle<Table_B>   y1 = Factory::make_y(); // the consumer does not possibly want to remember the scheme type of y.
    Scheme_handle<Table_B>   y2 = Factory::make_y(); // schemes arent singletons!
    //
    Scheme_B                 x2 = Factory::make_x(); // a table does not imply a scheme!
    // (but for each table there is a default compliant scheme, as defined by the mapping Scheme_handle : TableType -> SchemeType )
};

int main(){
    MyClass q;
}

I assume what I want to do is pretty standard. How is it supposed to be done?, i.e. how can the handle (without dynamic memory) be implemented?


Solution

  • A data-member of a class has to have a concrete type. There is no mechanism to deduce it from an initialiser. What you can do is move the desired factory method into the type.

    template<auto FactoryMethod> class Scheme_for;
    
    template<typename TableType, TableType(*FactoryMethod)()> 
    class Scheme_for<FactoryMethod> : public Scheme_handle<TableType>{ 
        public: Scheme_for(): Scheme_handle<TableType>(FactoryMethod()){} 
    };
    

    Then you only mention the factory method once per use.

    struct MyClass{
        Scheme_for<Factory::make_x> x1;
        Scheme_for<Factory::make_y> y1;
        Scheme_for<Factory::make_y> y2;
        Scheme_B  x2 = Factory::make_x();
    };
    

    See it on godbolt