Skip to content

Commit 6d43aad

Browse files
committed
span: Make Span template deduction guides work in SFINAE context
Also add test to make sure this doesn't get broken in the future. This was breaking vector<bool> serialization in multiprocess code because template current deduction guides would make it appear like vector<bool> could be converted to a span, but then the actual conversion to span would fail.
1 parent 8062c3b commit 6d43aad

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ BITCOIN_TESTS =\
144144
test/sigopcount_tests.cpp \
145145
test/skiplist_tests.cpp \
146146
test/sock_tests.cpp \
147+
test/span_tests.cpp \
147148
test/streams_tests.cpp \
148149
test/sync_tests.cpp \
149150
test/system_tests.cpp \

src/span.h

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,15 +222,32 @@ class Span
222222
template <typename O> friend class Span;
223223
};
224224

225+
// Return result of calling .data() method on type T. This is used to be able to
226+
// write template deduction guides for the single-parameter Span constructor
227+
// below that will work if the value that is passed has a .data() method, and if
228+
// the data method does not return a void pointer.
229+
//
230+
// It is important to check for the void type specifically below, so the
231+
// deduction guides can be used in SFINAE contexts to check whether objects can
232+
// be converted to spans. If the deduction guides did not explicitly check for
233+
// void, and an object was passed that returned void* from data (like
234+
// std::vector<bool>), the template deduction would succeed, but the Span<void>
235+
// object instantiation would fail, resulting in a hard error, rather than a
236+
// SFINAE error.
237+
// https://stackoverflow.com/questions/68759148/sfinae-to-detect-the-explicitness-of-a-ctad-deduction-guide
238+
// https://stackoverflow.com/questions/16568986/what-happens-when-you-call-data-on-a-stdvectorbool
239+
template<typename T>
240+
using DataResult = std::remove_pointer_t<decltype(std::declval<T&>().data())>;
241+
225242
// Deduction guides for Span
226243
// For the pointer/size based and iterator based constructor:
227244
template <typename T, typename EndOrSize> Span(T*, EndOrSize) -> Span<T>;
228245
// For the array constructor:
229246
template <typename T, std::size_t N> Span(T (&)[N]) -> Span<T>;
230247
// For the temporaries/rvalue references constructor, only supporting const output.
231-
template <typename T> Span(T&&) -> Span<std::enable_if_t<!std::is_lvalue_reference_v<T>, const std::remove_pointer_t<decltype(std::declval<T&&>().data())>>>;
248+
template <typename T> Span(T&&) -> Span<std::enable_if_t<!std::is_lvalue_reference_v<T> && !std::is_void_v<DataResult<T&&>>, const DataResult<T&&>>>;
232249
// For (lvalue) references, supporting mutable output.
233-
template <typename T> Span(T&) -> Span<std::remove_pointer_t<decltype(std::declval<T&>().data())>>;
250+
template <typename T> Span(T&) -> Span<std::enable_if_t<!std::is_void_v<DataResult<T&>>, DataResult<T&>>>;
234251

235252
/** Pop the last element off a span, and return a reference to that element. */
236253
template <typename T>

src/test/span_tests.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <span.h>
6+
7+
#include <boost/test/unit_test.hpp>
8+
#include <array>
9+
#include <set>
10+
#include <vector>
11+
12+
namespace {
13+
struct Ignore
14+
{
15+
template<typename T> Ignore(T&&) {}
16+
};
17+
template<typename T>
18+
bool Spannable(T&& value, decltype(Span{value})* enable = nullptr)
19+
{
20+
return true;
21+
}
22+
bool Spannable(Ignore)
23+
{
24+
return false;
25+
}
26+
27+
#if defined(__clang__)
28+
# pragma clang diagnostic push
29+
# pragma clang diagnostic ignored "-Wunneeded-member-function"
30+
# pragma clang diagnostic ignored "-Wunused-member-function"
31+
#endif
32+
struct SpannableYes
33+
{
34+
int* data();
35+
size_t size();
36+
};
37+
struct SpannableNo
38+
{
39+
void* data();
40+
size_t size();
41+
};
42+
#if defined(__clang__)
43+
# pragma clang diagnostic pop
44+
#endif
45+
} // namespace
46+
47+
BOOST_AUTO_TEST_SUITE(span_tests)
48+
49+
// Make sure template Span template deduction guides accurately enable calls to
50+
// Span constructor overloads that work, and disable calls to constructor overloads that
51+
// don't work. This makes it is possible to use the Span constructor in a SFINAE
52+
// contexts like in the Spannable function above to detect whether types are or
53+
// aren't compatible with Spans at compile time.
54+
//
55+
// Previously there was a bug where writing a SFINAE check for vector<bool> was
56+
// not possible, because in libstdc++ vector<bool> has a data() memeber
57+
// returning void*, and the Span template guide ignored the data() return value,
58+
// so the template substitution would succeed, but the constructor would fail,
59+
// resulting in a fatal compile error, rather than a SFINAE error that could be
60+
// handled.
61+
BOOST_AUTO_TEST_CASE(span_constructor_sfinae)
62+
{
63+
BOOST_CHECK(Spannable(std::vector<int>{}));
64+
BOOST_CHECK(!Spannable(std::set<int>{}));
65+
BOOST_CHECK(!Spannable(std::vector<bool>{}));
66+
BOOST_CHECK(Spannable(std::array<int, 3>{}));
67+
BOOST_CHECK(Spannable(Span<int>{}));
68+
BOOST_CHECK(Spannable("char array"));
69+
BOOST_CHECK(Spannable(SpannableYes{}));
70+
BOOST_CHECK(!Spannable(SpannableNo{}));
71+
}
72+
73+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)