Search code examples
c++templatesc++17variadic-templates

Generalizing std::conditional_t<>


I have a function that computes a certain object from a given parameter (say, an important node from a graph). Now, when calculating such an object, the function might allocate some memory. Sometimes I want the function to just return the result, and sometimes to return the result plus the memory used to compute it.

I typically solve this binary case like this:

enum class what {
    what1,  // return, e.g., just an int
    what2   // return, e.g., a std::pair<int, std::vector<int>>
};

template <what w>
std::conditional_t<w == what::what1, int, std::pair<int, std::vector<int>>>
calculate_something(const param& p) { ... }

I would like to generalize the solution above to longer enumerations

enum class list_whats {
    what1,
    what2,
    what3,
    what4,
    what5
};

One possible solution is to nest as many std::conditional_t as needed

template <list_whats what>
std::conditional_t<
    what == list_whats::what1,
    int,
    std::conditional_t<
        what == list_whats::what2,
        float,
        ....
    >
>
>
calculate_something(const param& p) { ... }

But this is cumbersome and perhaps not too elegant.

Does anyone know how to do this in C++ 17?

EDIT

To make the question perfectly clear: how do I implement the function return_something so as to be able to run the following main?

int main() {
    int s1 = return_something<list_whats::what1>();
    s1 = 3;

    float s2 = return_something<list_whats::what2>();
    s2 = 4.0f;

    double s3 = return_something<list_whats::what3>();
    s3 = 9.0;

    std::string s4 = return_something<list_whats::what4>();
    s4 = "qwer";

    std::vector<int> s5 = return_something<list_whats::what5>();
    s5[3] = 25;
}

Solution

  • I don't think you should use std::conditional at all to solve your problem. If I get this right, you want to use a template parameter to tell your function what to return. The elegant way to do this could look something like this:

    #include <vector>
    
    enum class what { what1, what2 };
    
    template <what W>
    auto compute() {
      if constexpr (W == what::what1) {
        return 100;
      }
      if constexpr (W == what::what2) {
        return std::pair{100, std::vector<int>{}};
      }
    }
    
    auto main() -> int {
      [[maybe_unused]] const auto as_int = compute<what::what1>();
      [[maybe_unused]] const auto as_pair = compute<what::what2>();
    }
    

    You can also use template specialization if you prefer another syntax:

    template <what W>
    auto compute();
    
    template <>
    auto compute<what::what1>() {
      return 100;
    }
    
    template <>
    auto compute<what::what2>() {
      return std::pair{100, std::vector<int>{}};
    }