[英]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);
}
};
如果你真的想保持讀取int
和float
的實現分開而不是使用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;
}
如果您不僅需要能夠read
入tuple
,則可以使其更加通用,而無需指向您要讀取的類型的指針。
// 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_map
、 vector
和string
進行演示。
根據@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.