簡體   English   中英

C++ 解壓可變模板參數並調用另一個模板方法

[英]C++ unpack variadic template parameters and call another template method

我正在嘗試解壓縮可變參數模板參數,並在每個參數上調用“read<>”。 然后收集“read<>”的結果創建一個新的元組object。

這是我在C++17學習“折疊表達”后寫的:

    template<typename ...U>
    std::tuple<U...> read_impl(std::tuple<U...>* arg) {
        // The argument exists for function overloading. As an alternative of partial specialization.
        using T = std::tuple<U...>;
        return T(read<U>(), ...);
    }

我得到了Fatal Error C1001 INTERNAL COMPILER ERROR

預期用途:

std::tuple<int, float, double> tup = read_impl(static_cast<std::tuple<int, float, double>*>(0));
// which is equivalent to
std::tuple<int, float, double> tup = std::make_tuple(read<int>(), read<float>(), read<double>());

這就是我目前正在做的事情,沒有解包魔法,它既丑陋又冗長。

    template<typename U1>
    std::tuple<U1> read_impl(std::tuple<U1>* arg) {
        using T = std::remove_pointer<decltype(arg)>::type;
        return T(read<U1>());
    }
    template<typename U1, typename U2>
    std::tuple<U1, U2> read_impl(std::tuple<U1, U2>* arg) {
        using T = std::remove_pointer<decltype(arg)>::type;
        return T(read<U1>(), read<U2>());
    }
    template<typename U1, typename U2, typename U3>
    std::tuple<U1, U2, U3> read_impl(std::tuple<U1, U2, U3>* arg) {
        using T = std::remove_pointer<decltype(arg)>::type;
        return T(read<U1>(), read<U2>(), read<U3>());
    }
    ...

這是一個最小的可重現案例:

#include <tuple>

class Reader {
public:
    template<typename T>
    T read() {
        return read_impl(static_cast<T*>(0));
    }
    int read_impl(int*) {
        return 1;
    }
    float read_impl(float*) {
        return 0.5f;
    }
    template<typename ...U>
    std::tuple<U...> read_impl(std::tuple<U...>* arg) {
        using T = std::tuple<U...>;
        return T(read<U>(), ...);
    }

    static void test() {
        Reader reader;
        auto result = reader.read<std::tuple<int, float>>();
        assert(std::get<0>(result) == 1);
        assert(std::get<1>(result) == 0.5f);
    }
};

int main() {
    Reader::test();
    return 0;
}

=== 回復一些評論/答案:===

@kiner_shah 為什么是元組的原始指針? 為什么不在 read_impl 中使用 arg?

因為如果我想根據不同的返回值專門化通用的“f”function,它不會編譯。

#include <exception>
#include <assert.h>

class Reader {
public:
    template<typename T>
    T f() {
        throw std::exception();
    }

    template<>
    int f() {
        return 1;
    }

    template<>
    float f() {
        return 0.5f;
    }

    static void test_f() {
        Reader reader;
        auto result = reader.f<int>();
        assert(result == 1);
    }
};

int main() {
    Reader::test_f();
    return 0;
}

=======

@Ted Lyngmo 您的最小可重現示例不使用任何編譯器進行編譯。

這就是我問這個問題的原因。 我想讓它編譯。

這是一個更完整的 Reader 示例。 它需要能夠讀取像這樣的復雜類型: reader.read<std::unordered_map<int, std::vector<std::string>>>()

#include "pch.h"
#include <unordered_map>
#include <unordered_set>
#include <tuple>
#include <string>
#include <optional>

class Reader {
public:
    template<typename T>
    T read() {
        return read_impl(static_cast<T*>(0));
    }

    template<typename T>
    T read_impl(T*) {
        T::deserialize(*this);
    }
    uint64_t read_impl(uint64_t*) {
        return 1; // this will not be a constant in the real situation.
    }
    uint32_t read_impl(uint32_t*) {
        return 1; // this will not be a constant in the real situation.
    }
    int read_impl(int*) {
        return 1; // this will not be a constant in the real situation.
    }
    float read_impl(float*) {
        return .5f; // this will not be a constant in the real situation.
    }
    bool read_impl(bool*) {
        return true; // this will not be a constant in the real situation.
    }
    std::string read_impl(std::string*) {
        return "readString"; // this will not be a constant in the real situation.
    }
    template<typename T>
    std::vector<T> read_impl(std::vector<T>*) {
        size_t size = read<uint64_t>();
        std::vector<T> r;
        r.reserve(size);
        for (size_t i = 0; i < size; i++) {
            r.push_back(read<T>());
        }
        return r;
    }
    template<typename TKey, typename TValue>
    std::unordered_map<TKey, TValue> read_impl(std::unordered_map<TKey, TValue>*) {
        size_t size = read<uint64_t>();
        std::unordered_map<TKey, TValue> r;
        r.reserve(size);
        for (size_t i = 0; i < size; i++) {
            TKey k = read<TKey>();
            TValue v = read<TValue>();
            r[k] = v;
        }
        return r;
    }
    template<typename T>
    std::unordered_set<T> read_impl(std::unordered_set<T>*) {
        size_t size = read<uint64_t>();
        std::unordered_set<T> r;
        r.reserve(size);
        for (size_t i = 0; i < size; i++) {
            r.insert(read<T>());
        }
        return r;
    }
    template<typename ...U>
    std::tuple<U...> read_impl(std::tuple<U...>* arg) {
        using T = std::tuple<U...>;
        return T{ read<U>() ... };
    }
    template<typename T>
    std::optional<T> read_impl(std::optional<T>* t) {
        bool hasValue = read<bool>();
        std::optional<T> r;
        if (hasValue) {
            r = read<T>();
        }
        return r;
    }

    static void test() {
        Reader reader;
        auto result = reader.read<std::tuple<int, float>>();
        assert(std::get<0>(result) == 1);
        assert(std::get<1>(result) == 0.5f);
        auto result2 = reader.read<std::unordered_map<int, std::vector<std::string>>>();
        assert(result2[1] == std::vector<std::string>{"readString"});
    }
};

int main() {
    Reader::test();
    return 0;
}

=======

@HolyBlackCat 和@Jarod42 非常感謝您指出代碼中的求值順序問題。

我會放棄專業化並使用if constexpr 您也不需要指向tuple的指針。

例子:

class Reader {
public:
    template <typename T>
    constexpr std::tuple<T> read() {
        // return different values depending on type:
        if constexpr (std::is_same_v<T, int>) return 1;
        if constexpr (std::is_same_v<T, float>) return 0.5f;
        return {};
    }

    // an overload that requires at least 2 template parameters:
    template <typename T, typename U, typename... V>
    constexpr std::tuple<T, U, V...> read() {
        return std::tuple_cat(read<T>(), read<U>(), read<V>()...);
    }

    static void test() {
        Reader reader;
        constexpr auto result = reader.read<int, float>();
        static_assert(std::is_same_v<decltype(result),
                                     const std::tuple<int, float>>);
        static_assert(std::get<0>(result) == 1);
        static_assert(std::get<1>(result) == 0.5f);
    }
};

演示

如果你真的想保持讀取intfloat的實現分開而不是使用if constexpr ,你可以:

    template <typename T>
    requires std::is_same_v<T, int>
    constexpr std::tuple<T> read() {
        return 1;
    }
    
    template <typename T>
    requires std::is_same_v<T, float>
    constexpr std::tuple<T> read() {
        return 0.5f;
    }

如果您不僅需要能夠readtuple ,則可以使其更加通用,而無需指向您要讀取的類型的指針。

// type trait to check if a type is a tuple:
template <typename...> struct is_tuple : std::false_type {};
template <typename... T> struct is_tuple<std::tuple<T...>> : std::true_type {};
template<class T>
inline constexpr bool is_tuple_v = is_tuple<T>::value;

class Reader {
public:
    template <typename T>
    requires std::is_same_v<T, int>
    constexpr int read() { return 1; }

    template <typename T>
    requires std::is_same_v<T, float>
    constexpr float read() { return 0.5f; }

    // and it supports more complex types, like tuples:
    template <typename T>
    requires is_tuple_v<T>
    constexpr T read() {
        return [this]<std::size_t... I>(std::index_sequence<I...>) -> T {
            return {this->read<std::tuple_element_t<I, T>>()...};
        }(std::make_index_sequence<std::tuple_size_v<T>>{});
    }

    static void test() {/* same as above */ }
};

演示


添加對unordered_map的支持將遵循與tuple相同的模式:

// type trait:
template <typename...> struct is_unordered_map : std::false_type {};
template<class Key, class T, class Hash, class KeyEqual, class Allocator>
struct is_unordered_map<std::unordered_map<Key,T,Hash,KeyEqual,Allocator>> :
    std::true_type {};
template<class T>
inline constexpr bool is_unordered_map_v = is_unordered_map<T>::value;

// ...
    template<typename T>
    requires is_unordered_map_v<T>
    T read() {
        using TKey = typename T::key_type;
        using TValue = typename T::mapped_type;
        std::size_t size = read<std::uint64_t>();
        T r;
        r.reserve(size);
        for (std::size_t i = 0; i < size; i++) {
            TKey k = read<TKey>();
            TValue v = read<TValue>();
            r[k] = v;
            //or, if the first Key value read should be kept:
            //r.emplace(read<TKey>(), read<TValue>());
        }
        return r;
    }

使用unordered_mapvectorstring進行演示

根據@HolyBlackCat 和@Jarod42 的評論, T(read<U>(), ...)應該是T{ read<U>()... }

    template<typename ...U>
    std::tuple<U...> read_impl(std::tuple<U...>* arg) {
        using T = std::tuple<U...>;
        return T{ read<U>() ... };
    }

我正在閱讀折疊表達式功能 ( https://en.cppreference.com/w/cpp/language/fold ),現在我了解到“折疊/減少”與“擴展”不同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM