Search code examples
c++template-meta-programming

How to select a class member using templates


Consider the example code below.

#include <iostream>

enum class FruitType {
    APPLE,
    ORANGE
};

class Fruit {
    uint64_t apple_count;
    uint64_t orange_count;

public:
    Fruit() : apple_count(0), orange_count(0) {}
    void increase_count(FruitType type) {
        if (type == FruitType::APPLE)
            apple_count++;
        else
            orange_count++;
    }
    void print() {
        std::cout << "apple_count: " << apple_count << std::endl;
        std::cout << "orange_count: " << orange_count << std::endl;
    }
};

int main(int, char**) {
    Fruit fruit;
    fruit.increase_count(FruitType::APPLE);
    fruit.increase_count(FruitType::ORANGE);
    fruit.increase_count(FruitType::APPLE);
    fruit.increase_count(FruitType::APPLE);
    fruit.increase_count(FruitType::ORANGE);
    fruit.print();

    return 0;
}

I want the increase_count function to effectively compile to increase_count_apple and increase_count_orange. This is a simplified example of another class where such compile-time optimisation helps me at runtime, in theory. Intuitively, this feels possible using templates, but I am unable to arrive at a satisfactory output.


Solution

  • By making increase_count a template that takes a non-type template paramter you can do

    class Fruit {
        uint64_t apple_count;
        uint64_t orange_count;
    
    public:
        Fruit() : apple_count(0), orange_count(0) {}
        template <FruitType type>
        void increase_count() {
            if constexpr (type == FruitType::APPLE)
                apple_count++;
            else
                orange_count++;
        }
        void print() {
            std::cout << "apple_count: " << apple_count << std::endl;
            std::cout << "orange_count: " << orange_count << std::endl;
        }
    };
    
    int main(int, char**) {
        Fruit fruit;
        fruit.increase_count<FruitType::APPLE>();
        fruit.increase_count<FruitType::ORANGE>();
        fruit.increase_count<FruitType::APPLE>();
        fruit.increase_count<FruitType::APPLE>();
        fruit.increase_count<FruitType::ORANGE>();
        fruit.print();
    
        return 0;
    }
    

    which removes the branch at compile time and fruit.increase_count<FruitType::APPLE> effectively calls increase_count_apple and fruit.increase_count<FruitType::ORANGE>() calls increase_count_orange.