1

I have a virtual class BASE, and an inherited class BOX_strwhich implements the virtual functions

class BASE {
public:
    virtual ~BASE() {};

    virtual std::vector<int> custom_int() const = 0;

    virtual std::vector<double> custom_double() const = 0;
};

struct BOX_str final : public BASE {

    template <typename ...Args>
    BOX_str(Args... args) : base(std::forward<Args>(args)...) {}

    std::string base;

    std::vector<int> custom_int() const {
        return custom_vec<int>(base);
    }

    std::vector<double> custom_double() const {
        return custom_vec<double>(base);
    }

    template <typename NUM>
    std::vector<NUM> custom_vec(const std::string& s) const {
        return {32, 62};
    }
};

But all of the BOX_str class content, except for the custom_vec() function, is generic amongst similar classes,

so I've tried to make a template class BOX

template <typename T>
struct BOX : public virtual BASE {

    template <typename ...Args>
    BOX(Args... args) : base(std::forward<Args>(args)...) {}

    T base;

    std::vector<int> custom_int() const {
        return custom_vec<int>(base);
    }

    std::vector<double> custom_double() const {
        return custom_vec<double>(base);
    }

    template <typename NUM>
    std::vector<NUM> custom_vec(const T&) const;
};

and leave custom_vec() to be implemented later for each explicit specialization of BOX

template <>
struct BOX<std::string> {
    template <typename NUM>
    std::vector<NUM> custom_vec(const std::string& s) const {
        return {42, 52};
    }
};

then I tried to test the classes

int main() {
    std::string mystr{ "abcd" };

    BASE* v1 = static_cast<BASE*>(new BOX<std::string>(mystr));
    BASE* v2 = static_cast<BASE*>(new BOX_str(mystr));
}

v2 didn't raise any errors, but v1 did: excess elements in struct initializer, which means the the explicit specialization of BOX cannot access its constructor, and all of BOX's contents.

I'm quite stuck and can't figure out how to correctly implement the BOX class so that it would work like BOX_str but in a generic manner. Would appreciate some assistance.

4
  • Why do you make the constructor a variadic template ? BASE constructor do not take any argument so it's useless to forward arguments for any derived class since nothing, except no argument, is eligible. Commented Nov 6, 2023 at 12:52
  • Moreover, a specialization is not a derived class. The specialization replaces the generic definition (in other words, what you really want is a "subderived" class, not a specialization). Commented Nov 6, 2023 at 12:54
  • You can't do partial specialization of classes or structures. You must do a complete, full reimplementation. Commented Nov 6, 2023 at 13:11
  • Terminology: BASE is an abstract class because it has (one or more) pure virtual functions. The term "virtual class" doesn't really mean anything, and could be confused with "virtual base". Commented Nov 6, 2023 at 13:17

1 Answer 1

2

As always, any problem* can be solved by an additional abstraction layer:

Define BOX::custom_vec to call a yet-to-be-defined ::custom_vec:

template <typename T>
struct BOX : public virtual BASE {
    // [...]

    template <typename NUM>
    std::vector<NUM> custom_vec(const T& v) const {
        return ::custom_vec<T, NUM>{}(v);
    }

Then, provide a struct that can be specialized and fully redefined without useless duplication of code:

template<class BoxT, class BoxN>
struct custom_vec {};

template<>
struct custom_vec<std::string, int>
{ std::vector<int> operator()(std::string const&) const { return {42, 52}; } };

template<>
struct custom_vec<std::string, double>
{ std::vector<double> operator()(std::string const&) const { return {1.618, 3.14}; } };

Demo on Compiler Explorer


*except having too many abstraction layers

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the solution, what does the empty double colons return ::custom_vec<T, NUM>{}(v) mean?
This is the scope "operator". It makes the name lookup switche from unqualified name lookup to qualified name lookup. Here, the effect of the switch is to find custom_vec from the global namespace instead of BOX::custom_vec.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.