Search code examples
c++templatestemplate-meta-programming

policy base design: conditionally change a member variable from policy class


My program defines an Animal struct that can be configured with CanSwim/CanNotSwim and CanBark/CanNotBark:

#include <iostream>

struct CanSwim {
};

struct CanNotSwim {
};

struct CanBark {
    CanBark() : volume(10) {}
    void bark() {
        std::cout << "bark at volume " << volume << std::endl;
    }

    void setVolume(int newVolume) {
        volume = newVolume;
    }

private:
    int volume;
};

struct CanNotBark {
};

template<class SwimType, class BarkType>
struct Animal : public SwimType, public BarkType {
    Animal() = default;
};

int main() {
    auto dog = Animal<CanSwim, CanBark>();
    dog.bark();

    auto cat = Animal<CanNotSwim, CanBark>();
    cat.bark();

    return 0;
}

Now how do I set a rule to automatically double CanBark::volume when an Animal is configured with class CanNotSwim (in compilation time)? To have:

    auto dog = Animal<CanSwim, CanBark>();
    dog.bark();
    // "bark at volume 10"

    auto cat = Animal<CanNotSwim, CanBark>();
    cat.bark();
    // "bark at volume 20"

I don't intend to move function bark() from CanBark to Animal.


Solution

  • With CRTP, you might got that information:

    struct CanSwim {};
    struct CanNotSwim {};
    
    template <typename Der>
    struct CanBark {
        CanBark() : volume(std::is_base_of_v<CanNotSwim, Der> ? 20 : 10) {}
        void bark() {
            std::cout << "bark at volume " << volume << std::endl;
        }
    
        void setVolume(int newVolume) { volume = newVolume; }
    
    private:
        int volume;
    };
    
    template <typename Der>
    struct CanNotBark {};
    
    template<class SwimType, template <typename> class BarkType>
    struct Animal : public SwimType, public BarkType<Animal<SwimType, BarkType>> {
        Animal() = default;
    };
    
    int main() {
        auto dog = Animal<CanSwim, CanBark>();
        dog.bark();
    
        auto cat = Animal<CanNotSwim, CanBark>();
        cat.bark();
    }
    

    Demo

    As alternative, you might provide the information in constructor:

    struct CanSwim {};
    struct CanNotSwim {};
    
    struct CanBark {
        CanBark(bool canSwim) : volume(canSwim ? 10 : 20) {}
        void bark() {
            std::cout << "bark at volume " << volume << std::endl;
        }
    
        void setVolume(int newVolume) { volume = newVolume; }
    
    private:
        int volume;
    };
    
    
    struct CanNotBark {
        CanNotBark(bool canSwim) {}
    };
    
    template<class SwimType, class BarkType>
    struct Animal : public SwimType, public BarkType {
        Animal() : BarkType(std::is_same_v<SwimType, CanSwim>) {}
    };
    
    int main() {
        auto dog = Animal<CanSwim, CanBark>();
        dog.bark();
    
        auto cat = Animal<CanNotSwim, CanBark>();
        cat.bark();
    }
    

    Demo