Home > Net >  C constructor behave differently according to input, with help of template
C constructor behave differently according to input, with help of template

Time:11-17

I'm trying to use the template to initialize an object. The code should assign s according to different parameters input, specifically "type". However, I can't get this to work.

#include <iostream>
#include <cmath>

using namespace std;

// different discount

class Strategy {
public:
    virtual double GetResult(double) = 0;
};

class SellNormal: public Strategy {
public:
    double GetResult(double original) override {
        return original;
    }
};

class SellDiscount: public Strategy {
    double rate;
public:
    SellDiscount(double r){
        rate = r;
    }
    double GetResult(double original) override {
        return original * rate;
    }
};
class SellReturn: public Strategy {
    int fulfill;
    int reduce;
public:
    SellReturn(int f, int r): fulfill(f), reduce(r){}
    double GetResult(double original) override {
        return original - (int(original) / fulfill) * reduce;
    }
};
class SellContext{
    Strategy* s;
public:
    enum type{
        NORMAL,
        DISCOUNT,
        RETURN
    };
    template<typename ...Ts>
    SellContext(type t, Ts... args){
        switch(t){
        case NORMAL:
            s = new SellNormal();
            break;
        case DISCOUNT:
            // double
            s = new SellDiscount(args...);
            break;
        case RETURN:
            // int, int
            s = new SellReturn(args...);
            break;
        }
    }
    double getResult(double original){
        return s->GetResult(original);
    }
};
    
int main(){
    auto c1 = new SellContext(SellContext::type::NORMAL);
    auto c2 = new SellContext(SellContext::type::DISCOUNT, 0.8);
    auto c3 = new SellContext(SellContext::type::RETURN, 300, 100);
    cout << c1->getResult(1000);
    cout << c2->getResult(1000);
    cout << c3->getResult(1000);
}

It's telling me that C can't find the appropriate constructor. How should I deal with this elegantly? I know I could achieve this by overloading constructors with different parameters. But isn't that too verbose?

The errors are as below

s

trategy_pattern_with_simple_factory.cpp: In instantiation of 'SellContext::SellContext(SellContext::type, Ts ...) [with Ts = {}]':
strategy_pattern_with_simple_factory.cpp:71:56:   required from here
strategy_pattern_with_simple_factory.cpp:57:17: error: no matching function for call to 'SellDiscount::SellDiscount()'
             s = new SellDiscount(args...);
                 ^~~~~~~~~~~~~~~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:23:5: note: candidate: 'SellDiscount::SellDiscount(double)'
     SellDiscount(double r){
     ^~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:23:5: note:   candidate expects 1 argument, 0 provided
strategy_pattern_with_simple_factory.cpp:20:7: note: candidate: 'constexpr SellDiscount::SellDiscount(const SellDiscount&)'
 class SellDiscount: public Strategy {
       ^~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:20:7: note:   candidate expects 1 argument, 0 provided
strategy_pattern_with_simple_factory.cpp:20:7: note: candidate: 'constexpr SellDiscount::SellDiscount(SellDiscount&&)'
strategy_pattern_with_simple_factory.cpp:20:7: note:   candidate expects 1 argument, 0 provided
strategy_pattern_with_simple_factory.cpp:61:17: error: no matching function for call to 'SellReturn::SellReturn()'
             s = new SellReturn(args...);
                 ^~~~~~~~~~~~~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:35:5: note: candidate: 'SellReturn::SellReturn(int, int)'
     SellReturn(int f, int r): fulfill(f), reduce(r){}
     ^~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:35:5: note:   candidate expects 2 arguments, 0 provided
strategy_pattern_with_simple_factory.cpp:31:7: note: candidate: 'constexpr SellReturn::SellReturn(const SellReturn&)'
 class SellReturn: public Strategy {
       ^~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:31:7: note:   candidate expects 1 argument, 0 provided
strategy_pattern_with_simple_factory.cpp:31:7: note: candidate: 'constexpr SellReturn::SellReturn(SellReturn&&)'
strategy_pattern_with_simple_factory.cpp:31:7: note:   candidate expects 1 argument, 0 provided
strategy_pattern_with_simple_factory.cpp: In instantiation of 'SellContext::SellContext(SellContext::type, Ts ...) [with Ts = {double}]':
strategy_pattern_with_simple_factory.cpp:72:63:   required from here
strategy_pattern_with_simple_factory.cpp:61:17: error: no matching function for call to 'SellReturn::SellReturn(double&)'
             s = new SellReturn(args...);
                 ^~~~~~~~~~~~~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:35:5: note: candidate: 'SellReturn::SellReturn(int, int)'
     SellReturn(int f, int r): fulfill(f), reduce(r){}
     ^~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:35:5: note:   candidate expects 2 arguments, 1 provided
strategy_pattern_with_simple_factory.cpp:31:7: note: candidate: 'constexpr SellReturn::SellReturn(const SellReturn&)'
 class SellReturn: public Strategy {
       ^~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:31:7: note:   no known conversion for argument 1 from 'double' to 'const SellReturn&'
strategy_pattern_with_simple_factory.cpp:31:7: note: candidate: 'constexpr SellReturn::SellReturn(SellReturn&&)'
strategy_pattern_with_simple_factory.cpp:31:7: note:   no known conversion for argument 1 from 'double' to 'SellReturn&&'
strategy_pattern_with_simple_factory.cpp: In instantiation of 'SellContext::SellContext(SellContext::type, Ts ...) [with Ts = {int, int}]':
strategy_pattern_with_simple_factory.cpp:73:66:   required from here
strategy_pattern_with_simple_factory.cpp:57:17: error: no matching function for call to 'SellDiscount::SellDiscount(int&, int&)'
             s = new SellDiscount(args...);
                 ^~~~~~~~~~~~~~~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:23:5: note: candidate: 'SellDiscount::SellDiscount(double)'
     SellDiscount(double r){
     ^~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:23:5: note:   candidate expects 1 argument, 2 provided
strategy_pattern_with_simple_factory.cpp:20:7: note: candidate: 'constexpr SellDiscount::SellDiscount(const SellDiscount&)'
 class SellDiscount: public Strategy {
       ^~~~~~~~~~~~
strategy_pattern_with_simple_factory.cpp:20:7: note:   candidate expects 1 argument, 2 provided
strategy_pattern_with_simple_factory.cpp:20:7: note: candidate: 'constexpr SellDiscount::SellDiscount(SellDiscount&&)'
strategy_pattern_with_simple_factory.cpp:20:7: note:   candidate expects 1 argument, 2 provided

CodePudding user response:

First, Strategy must have a virtual destructor since you'll be destroying instances via base class pointers.

It looks like you are trying to use type as some sort of tag and you can't explicitly supply template parameters to the constructor. That would be a template parameter to the class itself - and SellContext is not a class template, so using tags is a good idea. They do need to be of different types though, so I suggest creating separate tag types and also to make the pointer into a smart pointer.

Example:

#include <memory>

class SellContext{
    std::unique_ptr<Strategy> s; // <- smart pointer

public:
    // tag types and tag instances:
    static constexpr struct NORMAL {} normal_tag{};
    static constexpr struct DISCOUNT {} discount_tag{};
    static constexpr struct RETURN {} return_tag{};

    // The constructors you need. No `switch` needed:
    template<class... Args>
    SellContext(NORMAL, Args&&... args) :
        s(std::make_unique<SellNormal>(std::forward<Args>(args)...)) {}

    template<class... Args>
    SellContext(DISCOUNT, Args&&... args) :
        s(std::make_unique<SellDiscount>(std::forward<Args>(args)...)) {}
    
    template<class... Args>
    SellContext(RETURN, Args&&... args) :
        s(std::make_unique<SellReturn>(std::forward<Args>(args)...)) {}

    double getResult(double original){
        return s->GetResult(original);
    }
};

Using them would then be done like this:

int main(){
    auto c1 = std::make_unique<SellContext>(SellContext::normal_tag);
    auto c2 = std::make_unique<SellContext>(SellContext::discount_tag, 0.8);
    auto c3 = std::make_unique<SellContext>(SellContext::return_tag, 300, 100);
    
    std::cout << c1->getResult(1000) << '\n';
}

Demo

Using constexpr-if:

#include <type_traits>

class SellContext{
    std::unique_ptr<Strategy> s;

public:
    static constexpr struct NORMAL {} normal_tag{};
    static constexpr struct DISCOUNT {} discount_tag{};
    static constexpr struct RETURN {} return_tag{};

    template<class Tag, class... Args>
    SellContext(Tag, Args&&... args) {
        static_assert(std::is_same_v<NORMAL, Tag> || 
                      std::is_same_v<DISCOUNT, Tag> ||
                      std::is_same_v<RETURN, Tag>);

        if constexpr(std::is_same_v<NORMAL, Tag>)
            s = std::make_unique<SellNormal>(std::forward<Args>(args)...);
        if constexpr(std::is_same_v<DISCOUNT, Tag>)
            s = std::make_unique<SellDiscount>(std::forward<Args>(args)...);
        if constexpr(std::is_same_v<RETURN, Tag>)
            s = std::make_unique<SellReturn>(std::forward<Args>(args)...);
    }

    double getResult(double original){
        return s->GetResult(original);
    }
};

Demo

Since the current constructors of the Strategy objects are all different, you could make it even simpler and remove the tags:

class SellContext{
    std::unique_ptr<Strategy> s;

public:
    SellContext() : s(std::make_unique<SellNormal>()) {}
    SellContext(double x) : s(std::make_unique<SellDiscount>(x)) {}
    SellContext(double x, double y) : s(std::make_unique<SellReturn>(x, y)) {}

    double getResult(double original){
        return s->GetResult(original);
    }
};
int main(){
    auto c1 = std::make_unique<SellContext>();
    auto c2 = std::make_unique<SellContext>(0.8);
    auto c3 = std::make_unique<SellContext>(300, 100);

    std::cout << c1->getResult(1000) << '\n';
}

Demo

  • Related