Search code examples
c++templatesdesign-patterns

C++ templated type traits class with template parameters referencing type traits class being defined


In the following code I use a type traits class Config to parameterize the strategies to be used in a zero cost abstraction. This compiles and runs correctly, but how can I templatize the traits class with the strategies to be used?

namespace strategies {
    namespace initializers {
        template<typename Config> struct initializeA {
            constexpr   void operator()(typename Config::TEnvironment& v, double* weights) {
                for (int i = 0; i < v.ncols; i++) v.data[i] = weights[i];
                typename Config::Normalizer{}(v, v.data);
            }
        };
    }
    namespace updaters {
        template<typename Config> struct updaterA {
            constexpr   void operator()(typename Config::TEnvironment& v, double* weights) {
                for (int i = 0; i < v.ncols; i++) v.data[i] *= weights[i];
                typename Config::Normalizer{}(v, v.data);
            }
        };
    }
    namespace normalizers {
        template<typename Config> struct normalizerA {
            constexpr   void operator()(typename Config::TEnvironment& v, double* weights) {
                double sum = 0;
                for (int i = 0; i < v.ncols; i++) sum += weights[i];
                for (int i = 0; i < v.ncols; i++) weights[i] /= sum;
            }
        };
    }
}
template<int NCols> struct Environment {
    static const int ncols = NCols;
    double data[NCols];
};

template <typename TConfig>
class Runner {
public:
    typename TConfig::Environment env{};
    Runner() {}
    void doRun() {
        double weights[env.ncols];
        std::fill(weights, weights + env.ncols, 10.0);
        typename TConfig::Initializer{}(env, weights); //initialize & normalize env.data
        for (int i = 0; i < env.ncols; i++) weights[i] = (1 + i) / 10.0;
        typename TConfig::Updater{}(env, weights); //update & normalize env.data
    }
};

template<int NCols>
struct Config {
    using TEnvironment = Environment<NCols>;
    typedef TEnvironment Environment;
    typedef strategies::initializers::initializeA<Config> Initializer;
    typedef strategies::updaters::updaterA<Config> Updater;
    typedef strategies::normalizers::normalizerA<Config> Normalizer;
};
template<int NCols, typename InitializerT, typename UpdaterT, typename NormalizerT>
struct Config2 {
    using TEnvironment = Environment<NCols>;
    typedef TEnvironment Environment;
    typedef InitializerT Initializer; //strategies::initializers::initializeA<Config2> 
    typedef UpdaterT Updater; // strategies::updaters::updaterA<Config2>
    typedef NormalizerT Normalizer; // strategies::normalizers::normalizerA<Config2>
};

TEST_CLASS(ScratchTests) {
    TEST_METHOD(testScratch) {
        //this compiles & runs, specifying fixed types for Initializer/Updater directly inside Config
        Runner<Config<5>> runner{}; //instantiate with NCols = 5
        runner.doRun();
        Assert::IsTrue(runner.env.data[0] > 0.06 && runner.env.data[0] < 0.07);

        // how can I achieve the following, where the client specifies the types for Initializer/Updater/Normalizer, given these rely on TConfig to get the types of Normalizer?
        /*
        Runner<Config2<5, strategies::initializers::initializeA<Config2>, strategies::updaters::updaterA<Config2>, strategies::normalizers::normalizerA<Config2>> runner2{}; //instantiate with NCols = 5 and specify types for Initializer, Updater, Normalizer
        runner2.doRun();
        Assert::IsTrue(runner2.env.data[0] > 0.06 && runner2.env.data[0] < 0.07);
        */
    }
};

For eg something like traits class Config2 above, but I am not sure how to use it, given the strategy template parameters are themselves templated with the config type, as given in the last example (Visual Studio complains on missing parameters for the Config2 parameter to Initialize/Updater/Normalizer)... Is there a way to perhaps use some sort of intermediate typedef? Ideally in c++20, but open to later versions.


Solution

  • You could do

    template<int NCols>
    struct ConfigA : public Config2<NCols,
        strategies::initializers::initializeA<ConfigA<NCols>>,
        strategies::updaters::updaterA<ConfigA<NCols>>,
        strategies::normalizers::normalizerA<ConfigA<NCols>>>
    {};
    
    Runner<ConfigA<5>> runner2{};
    

    Demo