作为练习,我试图为C ++ 17提出一个元组实现。显然,有些麻烦,因为我猜Clang能够正常工作,但是GCC segfaults。首先是MVCE:
#include <utility>
#include <string>
#include <type_traits>
#include <iostream>
namespace cho {
template<std::size_t N, typename T, typename... types>
struct get_Nth_type
{
using type = typename get_Nth_type<N - 1, types...>::type;
};
template<typename T, typename... types>
struct get_Nth_type<0, T, types...>
{
using type = T;
};
template<std::size_t N, typename... Args>
using get_type = typename get_Nth_type<N, Args...>::type;
template <std::size_t I, typename T>
struct tuple_leaf {
static auto constexpr ix = I;
T elem;
};
template<class Seq, class... Ts> struct tuple_impl;
template<size_t... Ix, class... Ts>
struct tuple_impl<std::index_sequence<Ix...>, Ts...> : tuple_leaf<Ix, Ts>... { };
template<typename... Ts>
struct tuple : tuple_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...> {};
template <std::size_t I, typename... Ts>
constexpr auto& get(tuple<Ts...> const& t) {
using T = get_type<I, Ts...>;
return static_cast<tuple_leaf<I, T>&>(const_cast<tuple<Ts...>&>(t)).elem;
}
template <std::size_t I, typename Ts>
struct tuple_element;
template <std::size_t I, typename... Ts>
struct tuple_element<I, tuple<Ts...>> {
using type = get_type<I, Ts...>;
};
template <std::size_t I, typename Tuple>
using tuple_element_t = typename tuple_element<I, Tuple>::type;
template <typename... Args>
constexpr auto make_tuple(Args&&... args) {
return tuple<Args...>{std::forward<Args>(args)...};
}
template <typename Tuple, typename T, std::size_t... Ix>
auto constexpr pb_impl(Tuple const& t, T v, std::index_sequence<Ix...> const&) {
return cho::make_tuple(((void)(0), get<Ix>(t))..., v);
}
template <typename T, typename... Ts>
auto constexpr push_back(tuple<Ts...> const& t, T const& v) {
return pb_impl(t, std::move(v), std::make_index_sequence<sizeof...(Ts)>());
}
}
int main() {
auto spt = cho::push_back(cho::make_tuple(42, 3.14f), std::string("e"));
std::cout << cho::get<0>(spt) << "\n";
cho::get<2>(spt) = std::string("pi");
static_assert(std::is_same_v<cho::tuple_element_t<0, decltype(spt)>, int&>);
auto st2 = cho::make_tuple(42, 3.14f);
static_assert(std::is_same_v<cho::tuple_element_t<0, decltype(st2)>, int>);
auto spt2 = cho::push_back(st2, std::string("e"));
cho::get<2>(spt2) = std::string("pi");
}
我也要说Clang有时也会出现段错误。简而言之,这段代码确实有一些不好的地方。罪魁祸首是元组的push_back
实现。我想返回一个新的元组,其中包含旧元组的副本和最后添加的一个元素。我相信,如果您看一下第一个元组示例,我尝试在其中打印第一个元素,那就是我得到的段错误。
pb_impl
通过为每个元素调用make_tuple
来调用get
,这将返回对旧元组元素的引用。然后,如果您尝试像我在以下方法中那样使用临时工,就会出现问题:
auto spt = cho::push_back(cho::make_tuple(42, 3.14f), std::string("e"));
在这里,push_back
将获得一个临时元组以供使用,并将引用复制到结果元组中。然后spt
将保留对已破坏的内存部分的引用。我在这里做错了什么?如果我被临时传递了,如何在pb_impl
中复制元组的内容?
而且,我不确定您是否能够复制,但是,为什么Clang在大多数情况下都能正常工作,在没有任何段错误的情况下打印42?
请注意,更改此行:
return cho::make_tuple(((void)(0), get<Ix>(t))..., v);
对此
return cho::make_tuple(((void)(0), get<Ix>(t))..., std::move(v));
解决了问题,但我不明白为什么,也不知道这是否偶然。
答案 0 :(得分:5)
这是罪魁祸首:
template <typename... Args>
constexpr auto make_tuple(Args&&... args) {
return tuple<Args...>{std::forward<Args>(args)...};
}
您要返回的元组由传递给make_tuple
的类型组成,但这些都是引用。您不想要这个。您需要创建实际值的元组,并使用类似tie
的函数来创建引用元组。
这是写make_tuple
的唯一明智的方法,也是standard one works的写法:
对于类型...中的每个Ti,在VTypes ...中对应的Vi类型为 std :: decay :: type,除非应用std :: decay导致 某些X类型的std :: reference_wrapper,在这种情况下推导 类型是X&。
答案 1 :(得分:1)
评论太长,但这是-fsanitize = undefined和-fsanitize-address的输出:
$ /llvm/8.0.0/bin/clang++ deleteme.cpp -fsanitize=undefined -std=c++17
$ ./a.out
42
/llvm/8.0.0/bin/../include/c++/v1/string:2317:12: runtime error: reference binding to null pointer of type 'std::__1::basic_string<char>'
$ /llvm/8.0.0/bin/clang++ deleteme.cpp -fsanitize=address -std=c++17
$ ./a.out
=================================================================
==32167==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffee3c52820 at pc 0x00010bfae2f2 bp 0x7ffee3c527b0 sp 0x7ffee3c527a8
READ of size 4 at 0x7ffee3c52820 thread T0
#0 0x10bfae2f1 in main (a.out:x86_64+0x1000012f1)
#1 0x7fff56c39014 in start (libdyld.dylib:x86_64+0x1014)
Address 0x7ffee3c52820 is located in stack of thread T0 at offset 96 in frame
#0 0x10bfade3f in main (a.out:x86_64+0x100000e3f)
This frame has 12 object(s):
[32, 56) 'spt'
[96, 104) 'ref.tmp' <== Memory access at offset 96 is inside this variable
[128, 132) 'ref.tmp1'
[144, 148) 'ref.tmp2'
[160, 184) 'ref.tmp3'
[224, 248) 'ref.tmp7'
[288, 296) 'st2'
[320, 324) 'ref.tmp12'
[336, 340) 'ref.tmp13'
[352, 376) 'spt2'
[416, 440) 'ref.tmp16'
[480, 504) 'ref.tmp19'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope (a.out:x86_64+0x1000012f1) in main
Shadow bytes around the buggy address:
0x1fffdc78a4b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a4f0: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 f2
=>0x1fffdc78a500: f2 f2 f2 f2[f8]f2 f2 f2 f8 f2 f8 f2 f8 f8 f8 f2
0x1fffdc78a510: f2 f2 f2 f2 f8 f8 f8 f2 f2 f2 f2 f2 f8 f2 f2 f2
0x1fffdc78a520: f8 f2 f8 f2 f8 f8 f8 f2 f2 f2 f2 f2 f8 f8 f8 f2
0x1fffdc78a530: f2 f2 f2 f2 f8 f8 f8 f3 f3 f3 f3 f3 00 00 00 00
0x1fffdc78a540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x1fffdc78a550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==32167==ABORTING
Abort trap: 6