我试图创建一个元组模拟,以便使用相应的标签类型访问其元素,而不是索引。我提出了下一个解决方案(简化):
template<class T> struct tag { using type = T; };
using r = tag<double>;
using t = tag<double>;
using c = tag<int>;
template<class... Ts> class S
{
std::tuple<typename Ts::type&&...> data;
public:
S(typename Ts::type&&... args) : data(std::forward<typename Ts::type>(args)...) {}
};
int main()
{
r::type r0 = 0.;
const t::type t0 = 1.;
auto S0 = S<r, t, c>(r0, t0, 2); // <- error here
//auto T0 = std::forward_as_tuple(r0, t0, 2); // <- works!
}
然而它没有编译(gcc 7.2):
error: cannot bind rvalue reference of type ‘tag<double>::type&& {aka double&&}’ to lvalue of type ‘tag<double>::type {aka double}’
auto S0 = S<r, t, c>(r0, t0, 2);
^
note: initializing argument 1 of ‘S<Ts>::S(typename Ts::type&& ...) [with Ts = {tag<double>, tag<double>, tag<int>}]’
S(typename Ts::type&&... args) : data(std::forward<typename Ts::type>(args)...) {}
^
我发现std::forward_as_tuple
函数可以正确地推断出参数类型,所以我的观点是对我的类做同样的事情。什么提示我做错了什么?
UPD:初步说明不完整,抱歉。我的意图不是存储副本,而是存储引用(非const用于非const参数,const用于const和rvalue引用,类似于std::forward_as_tuple
所做的)。请参阅以下更新代码中的注释:
template<class... Ts> class S
{
std::tuple<typename Ts::type...> data;
public:
template<class... Args>
S(Args&&... args) : data(std::forward<Args>(args)...) {}
template<size_t I> auto& get()
{
return std::get<I>(data);
}
};
int main()
{
r::type r0 = 0.;
const t::type t0 = 1.;
auto S0 = S<r, t, c>(r0, t0, 2);
S0.get<0>() = 111; // <- r0 is not changed!
S0.get<1>() = 222; // <- must not be possible!
auto T0 = std::forward_as_tuple(r0, t0, 2);
std::get<0>(T0) = 333; // <- r0 == 333
std::get<1>(T0) = 444; // <- compile error -- can't change const!
}
答案 0 :(得分:1)
您需要将构造函数声明为模板:
#include <utility>
#include <tuple>
template<class T> struct tag { using type = T; };
using r = tag<double>;
using t = tag<double>;
using c = tag<int>;
template<class... Ts> class S
{
std::tuple<typename Ts::type...> data; // there is no need to use && here as we want tuple to contain items "as is", not references
public:
template<typename... TArgs>
S(TArgs && ... args) : data(std::forward<TArgs>(args)...) {}
};
int main()
{
r::type r0 = 0.;
const t::type t0 = 1.;
auto S0 = S<r, t, c>(r0, t0, 2); // <- error here
static_cast<void>(S0); // not used
//auto T0 = std::forward_as_tuple(r0, t0, 2); // <- works!
return(0);
}
我想提一下另一个问题:这种元组实际上不会让你按标签类型访问元素,因为它允许标签重复。如果您需要按标记类型进行访问,则需要检查标记&lt; - &gt;值类型关联更彻底。您可能还想查看一些existing tagged-tuple implementation。
答案 1 :(得分:0)
Ts::type&&
不是转发引用,因为Ts::type
不是模板参数,因此&&
引用右值引用。您不能将左值绑定到右值引用,因此错误。
std::forward_as_tuple
有效,因为您只将参数存储在“转发元组”中,而不是将它们存储在S
中。
只需将它们更改为const&
引用,因为它也可以绑定到rvalues和lvalues,如果你想存储引用(我假设你这样做):
template<class... Ts> class S
{
std::tuple<const typename Ts::type&...> data;
public:
S(const typename Ts::type&... args) : data(args...) {}
};
答案 2 :(得分:0)
我想我找到了解决方案。我们的想法是使用make-function创建一个S对象,如@VTT建议的那样。这个make-function复制类型修饰符(ref,const ref)并将它们添加到S模板参数中。然后,在S中,这些类型修饰符使用几个conditional_t
模板复制回元组参数(数据)。 decay_t
仅用于获取纯类型本身,而不是引用它(否则编译器,gcc72,error: ‘tag<double>&’ is not a class, struct, or union type
说; clang38更好一点:: error: type 'tag<double> &' cannot be used prior to '::' because it has no members
)
#include <tuple>
#include <utility>
#include <type_traits>
using namespace std;
// additional parameter is needed to distinguish r and t
template<class T, char c> struct tag { using type = T; };
using r = tag<double, 'r'>;
using t = tag<double, 't'>;
using c = tag<int, 'c'>;
//...
namespace details
{
template<size_t N, class T, class... Ts>
struct index_impl {
static constexpr size_t value = N; };
template<size_t I, class T, class Ti, class... Ts>
struct index_impl<I, T, Ti, Ts...> {
static constexpr size_t value =
std::is_same<T, Ti>::value? I : index_impl<I + 1, T, Ts...>::value; };
template<class T, class... Ts>
struct index {
static constexpr size_t value = index_impl<0, T, Ts...>::value; };
template<class T, class... Ts>
static constexpr size_t index_v = index<T, Ts...>::value;
} // namespace details
template<class... Ts> class S
{
std::tuple<
std::conditional_t<
std::is_reference<Ts>::value,
std::conditional_t<
std::is_const<std::remove_reference_t<Ts>>::value,
const typename std::decay_t<Ts>::type&,
typename std::decay_t<Ts>::type&
>,
typename std::decay_t<Ts>::type
>...
> data;
public:
template<class... Args>
S(Args&&... args) : data(std::forward<Args>(args)...) {}
template<class T> auto& get()
{
return std::get<details::index_v<T, std::decay_t<Ts>...>>(data);
}
};
template<class... Ts, class... Args> auto make_S(Args&&... args)
{
return S<
std::conditional_t<
std::is_reference<Args>::value,
std::conditional_t<
std::is_const<std::remove_reference_t<Args>>::value,
const Ts&, Ts&
>,
Ts
>...
>(std::forward<Args>(args)...);
}
int main()
{
r::type r0 = 0;
const t::type t0 = 1;
auto S0 = make_S<r, t, c>(r0, t0, 0);
S0.get<r>() = 111;
//S0.get<t>() = 222; // <- must not be possible!
S0.get<c>() = 333;
auto T0 = std::forward_as_tuple(r0, t0, 2);
std::get<0>(T0) = 444; // <- r0 == 333
//std::get<1>(T0) = 555; // <- compile error -- can't change const!
std::get<2>(T0) = 666;
}
我确信这个想法可以更好地实施,但这个解决方案有效!例如,我不太明白为什么is_const
不能用于引用,因此我必须使用remove_reference_t
从类型中删除它们。因此,最终的解决方案是过于复杂的IMO。