0

I have a generic class, where two of the generics are going to be identical 90% of the time, but on the off chance that they are separate types, I need to perform member initialization a little bit differently.

template<typename S, typename T = S>
class MyClass {
    MyClass(const Options<S> & opts) {
        if (std::is_same<S,T>::value) {
            thing.reset(new T(opts.get_s_thing()));   // 90% of time, use type S in args to initialize thing
        } else {
            thing.reset(new T());              // Special case, types differ, so create a new one
        }
        //
        // Other constructor things...
        //
    }

    std::shared_ptr<T> thing;
};

Only thing is, since type-checking is done at compile time, the thing.reset(opts.get_s_thing()); line throws an error if I create MyClass with an explicitly different type for T. But because the types differ, that line would never get called anyways. Is there some preprocessor directive I can use here? What strategy should I take?

Changing the constructor signature in MyClass is not an option, since this is part of a VERY large codebase. T is a new thing, so I have more freedom in deciding the "standard interface" for Ts.

Oren Bell
  • 460
  • 1
  • 5
  • 13

1 Answers1

2

I would separate the two constructors. And take the rest of the current constructor code apart, e.g. to an initialize function.

  1. [c++11] Using enable_if. [Demo]
template <typename S, typename T = S>
struct MyClass {
    void initialize() {
        std::cout << "initialize\n";
        //
        // Other constructor things...
        //
    }
    
    template <typename U = S>
    MyClass(const Options<typename std::enable_if<std::is_same<U,T>::value, U>::type>& opts) {
        std::cout << "Same same, ";
        thing.reset(new S(opts.get_s_thing()));
        initialize();
    }

    template <typename U = S>
    MyClass(const Options<typename std::enable_if<not std::is_same<U,T>::value, U>::type>& opts) {
        std::cout << "But different, ";
        thing.reset(new T());
        initialize();
    }
    
    std::shared_ptr<T> thing;
};
  1. [c++20] Using a requires clause. [Demo]
template <typename S, typename T = S>
struct MyClass {
    void initialize()
    {
        std::cout << "initialize\n";
        //
        // Other constructor things...
        //
    }
    
    MyClass(const Options<S>& opts) requires std::is_same<S,T>::value {
        std::cout << "Same same, ";
        thing.reset(new S(opts.get_s_thing()));
        initialize();
    }
    MyClass(const Options<S>& opts) {
        std::cout << "But different, ";
        thing.reset(new T());
        initialize();
    }
    std::shared_ptr<T> thing;
};

Other options if you want to keep the current signature completely unchanged are:

  1. [c++11] Using partial specializations of a helper class. [Demo]
    template <typename U, typename V, bool ConstructorsAreEqual>
    struct MyThingInitializer;
    template <typename U, typename V>
    struct MyThingInitializer<U, V, true> {
        static void initialize(const Options<U>& opts, std::shared_ptr<V> thing) {
            std::cout << "Same same\n";
            thing.reset(new V(opts.get_s_thing()));
        }
    };
    template <typename U, typename V>
    struct MyThingInitializer<U, V, false> {
        static void initialize(const Options<U>&, std::shared_ptr<V> thing) {
            std::cout << "But different\n";
            thing.reset(new V());
        }
    };

    MyClass(const Options<S>& opts) {
        MyThingInitializer<S, T, std::is_same<S, T>::value>::initialize(opts, thing);
        //
        // Other constructor things...
        //
    }
  1. [c++20] Using if constexpr. [Demo]
    MyClass(const Options<S>& opts) {
        if constexpr(std::is_same<S,T>::value)
        {
            std::cout << "Same same\n";
            thing.reset(new S(opts.get_s_thing()));
        }
        else
        {
            std::cout << "But different";
            thing.reset(new T());
        }
        //
        // Other constructor things...
        //
    }

In another order of things, it's not recommended to use new with smart pointers. You may want to change your lines:

thing.reset(new S(opts.get_s_thing()));
thing.reset(new T());

for:

std::make_shared<U>(opts.get_s_thing()).swap(thing);
std::make_shared<T>().swap(thing);
rturrado
  • 7,699
  • 6
  • 42
  • 62