Jes Jes - 3 years ago 223
C++ Question

Return a tuple corresponding to variadic template in C++17

How to return a tuple from a function that takes a list of variadic template? The tuple types should be the same to the argument types.

template <typename ... T, typename D>
std::tuple<T...> foo(D&& Duration) {
// How to do this?
if constexpr(sizeof... (args) > 0) {
// What to return?

The idea is to use C++17 structure binding so I can do something like this:

auto var = std::chrono::seconds(19874);

auto [h, m] = foo<std::chrono::hours, std::chrono::minutes> (var);
auto [m, s, ms] = foo<std::chrono::minutes, std::chrono::seconds, std::chrono::milliseconds>(var);

For both cases, the sum (h:m or m:s:ms) should be 19874 seconds.

Answer Source

This code should do what you ask. It constructs the tuple using an initializer list of calls to chrono_extract for each of the ouptut types. The initializer list parameters are processed in order according to the standard, so this shouldn't be sensitive to any reordering even though it might look like it from the use of the commas. The chrono_extract function casts the input duration to the type of the output duration, then subtracts the output from the input (which is passed by reference, so its value will be reflected in chrono_components). In order to correctly run the algorithm, we need to find the smallest time component in the list, and run all calculations at the smallest resolution (otherwise there will be duration casting errors). Also provided is an assertion to make sure that the return duration types are in decreasing order because the function will not operate correctly if they are in a different order. One possible enhancement that could be added is to also return a "remainder" in the tuple (that is the same as the input type) if there is anything left over from the decomposition (as in the first test case in the code below).

#include <chrono>
#include <iostream>
#include <tuple>
#include <type_traits>

template <typename lhs_t, typename rhs_t, typename... other_ts>
constexpr void assert_decreasing_ratios() {
    static_assert(std::ratio_greater_v<typename lhs_t::period,
                                       typename rhs_t::period>,
                  "Periods are non-decreasing.");
    if constexpr (sizeof...(other_ts)) {
        assert_decreasing_ratios<rhs_t, other_ts...>();

template <typename return_duration_t, typename input_duration_t>
return_duration_t chrono_extract(input_duration_t& value) {
    auto extracted = std::chrono::duration_cast<return_duration_t>(value);
    value -= extracted;
    return extracted;

template <typename... return_ts, typename duration_t>
std::tuple<return_ts...> chrono_components(duration_t value) {
    using smallest_t = std::tuple_element_t<sizeof...(return_ts) - 1,
    auto small_value = std::chrono::duration_cast<smallest_t>(value);
    return {chrono_extract<return_ts>(small_value)...};

int main()
    std::chrono::seconds before(19874);

        auto [h, m] = chrono_components<std::chrono::hours,
        std::chrono::seconds after(h + m);
        std::cout << h.count() << " hours " 
                  << m.count() << " minutes = "
                  << after.count() << " seconds\n";

        auto [m, s, ms] = chrono_components<std::chrono::minutes,
        auto after =
            std::chrono::duration_cast<std::chrono::seconds>(m + s + ms);
        std::cout << m.count() << " minutes " 
                  << s.count() << " seconds " 
                  << ms.count() << " milliseconds = "
                  << after.count() << " seconds\n";


5 hours 31 minutes = 19860 seconds
331 minutes 14 seconds 0 milliseconds = 19874 seconds

The first line shows that the seconds value has been truncated because it's not an exact number of minutes.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download