Workaround for gcc bug, fix for static global variable and function in header, fix of c++20 warning

This commit is contained in:
Hans Dembinski 2020-11-01 17:10:11 +01:00 committed by GitHub
parent 2f91f037b9
commit 0840102bfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 152 additions and 79 deletions

View File

@ -4,12 +4,9 @@
shallow_clone: true shallow_clone: true
max_jobs: 2 max_jobs: 2
matrix:
fast_finish: true
image: image:
- Visual Studio 2017 - Visual Studio 2017
- Ubuntu
branches: branches:
only: only:
@ -20,23 +17,17 @@ build: off
environment: environment:
B2_OPTS: -j2 -q warnings-as-errors=on B2_OPTS: -j2 -q warnings-as-errors=on
UBSAN_OPTIONS: print_stacktrace=1
LSAN_OPTIONS: verbosity=1:log_threads=1
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
install: install:
# clone minimal set of Boost libraries # clone minimal set of Boost libraries
- cd .. - cd ..
- cmd: git clone -b %APPVEYOR_REPO_BRANCH% --depth 5 https://github.com/boostorg/boost.git - git clone -b %APPVEYOR_REPO_BRANCH% --depth 5 https://github.com/boostorg/boost.git
- sh: git clone -b $APPVEYOR_REPO_BRANCH --depth 5 https://github.com/boostorg/boost.git
- cd boost - cd boost
- git submodule update --init --depth 5 tools/build tools/boostdep - git submodule update --init --depth 5 tools/build tools/boostdep
# replace Boost library with this project and install dependencies # replace Boost library with this project and install dependencies
- cmd: xcopy /s /e /q %APPVEYOR_BUILD_FOLDER% libs\histogram\ - xcopy /s /e /q %APPVEYOR_BUILD_FOLDER% libs\histogram\
- cmd: python tools\boostdep\depinst\depinst.py -N units -N range -N accumulators --git_args "--depth 5 --jobs 2" histogram - python tools\boostdep\depinst\depinst.py -N units -N range -N accumulators --git_args "--depth 5 --jobs 2" histogram
- sh: rm -rf libs/histogram ; mv $APPVEYOR_BUILD_FOLDER libs/histogram
- sh: python3 tools/boostdep/depinst/depinst.py --git_args "--depth 5 --jobs 2" histogram
# use hdembinski/serialization due to frequent errors in boostorg/serialization # use hdembinski/serialization due to frequent errors in boostorg/serialization
- cd libs/serialization - cd libs/serialization
@ -46,18 +37,11 @@ install:
- cd ../.. - cd ../..
# prepare Boost build # prepare Boost build
- cmd: cmd /c bootstrap & b2 headers & cd libs\histogram - cmd /c bootstrap & b2 headers & cd libs\histogram
- sh: ./bootstrap.sh; ./b2 headers; cd libs/histogram
test_script: test_script:
# on windows # on windows
- cmd: ..\..\b2 %B2_OPTS% cxxstd=latest test//minimal test//serialization - ..\..\b2 %B2_OPTS% cxxstd=latest test//minimal test//serialization
# on linux
- sh:
B2="../../b2 ${B2_OPTS}";
$B2 toolset=gcc-7 cxxstd=14 exception-handling=off rtti=off test//minimal &&
$B2 toolset=gcc-9 cxxstd=latest cxxflags="-O3 -funsafe-math-optimizations" examples test//all &&
$B2 toolset=clang-6 cxxstd=latest variant=histogram_ubasan test//all
## Uncomment the following to stop VM and enable interactive login. ## Uncomment the following to stop VM and enable interactive login.
## Instructions how to log into the Appveyor VM are automatically printed. ## Instructions how to log into the Appveyor VM are automatically printed.

View File

@ -9,8 +9,14 @@ on:
- 'doc/**' - 'doc/**'
- 'tools/**' - 'tools/**'
jobs: env:
B2_OPTS: -q -j2 warnings-as-errors=on
UBSAN_OPTIONS: print_stacktrace=1
LSAN_OPTIONS: verbosity=1:log_threads=1
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1
COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
jobs:
superproject_cmake_and_b2: superproject_cmake_and_b2:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
@ -27,15 +33,19 @@ jobs:
cd .. cd ..
cmake -DBOOST_ENABLE_CMAKE=1 -DBoost_VERBOSE=1 boost cmake -DBOOST_ENABLE_CMAKE=1 -DBoost_VERBOSE=1 boost
ctest -j2 --output-on-failure -R boost_histogram ctest -j2 --output-on-failure -R boost_histogram
- name: b2 cxxstd=14,17 exception-handling=on/off rtti=on/off - name: prepare b2
run: | run: |
cd $GITHUB_WORKSPACE/../boost cd ../boost
./bootstrap.sh ./bootstrap.sh
./b2 headers ./b2 headers
cd libs/histogram - name: b2 cxxstd=14 exception-handling=off rtti=off
B2="../../b2 -q -j2 warnings-as-errors=on" run: |
$B2 cxxstd=14,17 test//all examples cd ../boost/libs/histogram
$B2 cxxstd=14 exception-handling=off rtti=off test//minimal ../../b2 $B2_OPTS cxxstd=14 exception-handling=off rtti=off test//minimal
- name: b2 cxxstd=17
run: |
cd ../boost/libs/histogram
../../b2 $B2_OPTS cxxstd=17 test//all
cov: cov:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -48,9 +58,7 @@ jobs:
run: | run: |
python --version python --version
pip install cpp-coveralls pip install cpp-coveralls
- name: b2 toolset=gcc-8 cxxstd=latest coverage=on test//all - name: prepare
env:
COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
run: | run: |
cd .. cd ..
git clone -b $GITHUB_BASE_REF --depth 5 https://github.com/boostorg/boost.git git clone -b $GITHUB_BASE_REF --depth 5 https://github.com/boostorg/boost.git
@ -74,11 +82,43 @@ jobs:
# simulate bundled boost by moving the headers instead of symlinking # simulate bundled boost by moving the headers instead of symlinking
rm -rf boost/histogram.pp boost/histogram rm -rf boost/histogram.pp boost/histogram
mv -f libs/histogram/include/boost/* boost mv -f libs/histogram/include/boost/* boost
cd libs/histogram
B2="../../b2 -q -j2 warnings-as-errors=on" - name: test gcc-8 cxxstd=latest coverage=on test//all
run: |
cd ../boost/libs/histogram
# don't compile examples in coverage build, coverage must come from tests alone # don't compile examples in coverage build, coverage must come from tests alone
$B2 toolset=gcc-8 cxxstd=latest coverage=on test//all ../../b2 $B2_OPTS toolset=gcc-8 cxxstd=latest coverage=on test//all
# process and send coverage data # process and send coverage data
GCOV=gcov-8 tools/cov.sh $COVERALLS_TOKEN GCOV=gcov-8 tools/cov.sh $COVERALLS_TOKEN
stress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: prepare b2
run: |
cd ..
git clone -b $GITHUB_BASE_REF --depth 5 https://github.com/boostorg/boost.git
cd boost
git submodule update --init --depth 5 tools/build tools/boostdep
mv -f $GITHUB_WORKSPACE/* libs/histogram
python tools/boostdep/depinst/depinst.py --git_args "--depth 5 --jobs 3" histogram
# prepare build
./bootstrap.sh
./b2 headers
- name: test gcc-7 cxxstd=14
run: |
cd ../boost/libs/histogram
../../b2 $B2_OPTS toolset=gcc-7 cxxstd=14 test//all examples
- name: test gcc-10 cxxstd=20 -O3 -funsafe-math-optimizations
run: |
cd ../boost/libs/histogram
../../b2 $B2_OPTS toolset=gcc-10 cxxstd=20 cxxflags="-O3 -funsafe-math-optimizations" test//all examples
- name: test clang-6 cxxstd=17 ubsan asan
run: |
cd ../boost/libs/histogram
../../b2 $B2_OPTS toolset=clang-6 cxxstd=17 variant=histogram_ubasan test//all

View File

@ -17,7 +17,7 @@ project
: requirements : requirements
<implicit-dependency>/boost//headers <implicit-dependency>/boost//headers
<include>$(BOOST_ROOT) <include>$(BOOST_ROOT)
<toolset>clang:<cxxflags>"-pedantic -Wextra -Wsign-compare -Wstrict-aliasing -fstrict-aliasing" <toolset>clang:<cxxflags>"-pedantic -Wextra -Wsign-compare -Wstrict-aliasing -fstrict-aliasing -Wvexing-parse"
<toolset>gcc:<cxxflags>"-pedantic -Wextra -Wsign-compare -Wstrict-aliasing -fstrict-aliasing" <toolset>gcc:<cxxflags>"-pedantic -Wextra -Wsign-compare -Wstrict-aliasing -fstrict-aliasing"
<toolset>msvc:<cxxflags>"/bigobj" <toolset>msvc:<cxxflags>"/bigobj"
<toolset>intel-win:<cxxflags>"/bigobj" <toolset>intel-win:<cxxflags>"/bigobj"

View File

@ -28,8 +28,6 @@ namespace boost {
namespace histogram { namespace histogram {
namespace detail { namespace detail {
static axis::null_type null_value;
template <class Axis> template <class Axis>
struct value_type_deducer { struct value_type_deducer {
using type = using type =
@ -115,7 +113,8 @@ decltype(auto) metadata_impl(A&& a, decltype(a.metadata(), 0)) {
template <class A> template <class A>
axis::null_type& metadata_impl(A&&, float) { axis::null_type& metadata_impl(A&&, float) {
return detail::null_value; static axis::null_type null_value;
return null_value;
} }
} // namespace detail } // namespace detail
@ -381,7 +380,7 @@ decltype(auto) value(const Axis& axis, real_index_type index) {
template <class Result, class Axis> template <class Result, class Axis>
Result value_as(const Axis& axis, real_index_type index) { Result value_as(const Axis& axis, real_index_type index) {
return detail::try_cast<Result, std::runtime_error>( return detail::try_cast<Result, std::runtime_error>(
value(axis, index)); // avoid conversion warning axis::traits::value(axis, index)); // avoid conversion warning
} }
/** Returns axis index for value. /** Returns axis index for value.
@ -407,8 +406,9 @@ axis::index_type index(const variant<Ts...>& axis, const U& value) {
@param axis any axis instance @param axis any axis instance
*/ */
// gcc workaround: must use unsigned int not unsigned as return type
template <class Axis> template <class Axis>
constexpr unsigned rank(const Axis& axis) { constexpr unsigned int rank(const Axis& axis) {
(void)axis; (void)axis;
using T = value_type<Axis>; using T = value_type<Axis>;
// cannot use mp_eval_or since T could be a fixed-sized sequence // cannot use mp_eval_or since T could be a fixed-sized sequence
@ -417,9 +417,11 @@ constexpr unsigned rank(const Axis& axis) {
} }
// specialization for variant // specialization for variant
// gcc workaround: must use unsigned int not unsigned as return type
template <class... Ts> template <class... Ts>
unsigned rank(const axis::variant<Ts...>& axis) { unsigned int rank(const axis::variant<Ts...>& axis) {
return detail::variant_access::visit([](const auto& a) { return rank(a); }, axis); return detail::variant_access::visit(
[](const auto& a) { return axis::traits::rank(a); }, axis);
} }
/** Returns pair of axis index and shift for the value argument. /** Returns pair of axis index and shift for the value argument.
@ -441,7 +443,7 @@ std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value)); return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
}, },
[&value](auto& a) -> std::pair<index_type, index_type> { [&value](auto& a) -> std::pair<index_type, index_type> {
return {index(a, value), 0}; return {axis::traits::index(a, value), 0};
}, },
axis); axis);
} }

View File

@ -30,9 +30,9 @@ using common_axes = mp11::mp_cond<
// Non-PODs rank highest, then floats, than integers; types with more capacity are higher // Non-PODs rank highest, then floats, than integers; types with more capacity are higher
template <class Storage> template <class Storage>
static constexpr std::size_t type_rank() { constexpr std::size_t type_rank() {
using T = typename Storage::value_type; using T = typename Storage::value_type;
return !std::is_pod<T>::value * 10000 + std::is_floating_point<T>::value * 100 + return !std::is_arithmetic<T>::value * 10000 + std::is_floating_point<T>::value * 100 +
10 * sizeof(T) + 2 * is_array_like<Storage>::value + 10 * sizeof(T) + 2 * is_array_like<Storage>::value +
is_vector_like<Storage>::value; is_vector_like<Storage>::value;
; ;

View File

@ -91,7 +91,7 @@ BOOST_HISTOGRAM_DETAIL_DETECT(is_streamable, (std::declval<std::ostream&>() << t
BOOST_HISTOGRAM_DETAIL_DETECT(has_operator_preincrement, ++t); BOOST_HISTOGRAM_DETAIL_DETECT(has_operator_preincrement, ++t);
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_equal, (std::declval<const T&>() == BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_equal, (std::declval<const T&>() ==
std::declval<const U>())); std::declval<const U&>()));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_radd, BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_radd,
(std::declval<T&>() += std::declval<U>())); (std::declval<T&>() += std::declval<U>()));
@ -106,7 +106,7 @@ BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(has_operator_rdiv,
(std::declval<T&>() /= std::declval<U>())); (std::declval<T&>() /= std::declval<U>()));
BOOST_HISTOGRAM_DETAIL_DETECT_BINARY( BOOST_HISTOGRAM_DETAIL_DETECT_BINARY(
has_method_eq, (std::declval<const T>().operator==(std::declval<const U>()))); has_method_eq, (std::declval<const T&>().operator==(std::declval<const U&>())));
BOOST_HISTOGRAM_DETAIL_DETECT(has_threading_support, (T::has_threading_support)); BOOST_HISTOGRAM_DETAIL_DETECT(has_threading_support, (T::has_threading_support));

View File

@ -165,40 +165,63 @@ struct large_int : totally_ordered<large_int<Allocator>, large_int<Allocator>>,
} }
template <class U> template <class U>
std::enable_if_t<std::is_integral<U>::value, bool> operator<(const U& o) const std::enable_if_t<std::is_integral<U>::value, bool> operator<(
noexcept { const U& o) const noexcept {
assert(data.size() > 0u); assert(data.size() > 0u);
return data.size() == 1 && safe_less()(data[0], o); return data.size() == 1 && safe_less()(data[0], o);
} }
template <class U> template <class U>
std::enable_if_t<std::is_integral<U>::value, bool> operator>(const U& o) const std::enable_if_t<std::is_integral<U>::value, bool> operator>(
noexcept { const U& o) const noexcept {
assert(data.size() > 0u); assert(data.size() > 0u);
assert(data.size() == 1 || data.back() > 0u); // no leading zeros allowed assert(data.size() == 1 || data.back() > 0u); // no leading zeros allowed
return data.size() > 1 || safe_less()(o, data[0]); return data.size() > 1 || safe_less()(o, data[0]);
} }
template <class U> template <class U>
std::enable_if_t<std::is_integral<U>::value, bool> operator==(const U& o) const std::enable_if_t<std::is_integral<U>::value, bool> operator==(
noexcept { const U& o) const noexcept {
assert(data.size() > 0u); assert(data.size() > 0u);
return data.size() == 1 && safe_equal()(data[0], o); return data.size() == 1 && safe_equal()(data[0], o);
} }
template <class U> template <class U>
std::enable_if_t<std::is_floating_point<U>::value, bool> operator<(const U& o) const std::enable_if_t<std::is_floating_point<U>::value, bool> operator<(
noexcept { const U& o) const noexcept {
return operator double() < o; return operator double() < o;
} }
template <class U> template <class U>
std::enable_if_t<std::is_floating_point<U>::value, bool> operator>(const U& o) const std::enable_if_t<std::is_floating_point<U>::value, bool> operator>(
noexcept { const U& o) const noexcept {
return operator double() > o; return operator double() > o;
} }
template <class U> template <class U>
std::enable_if_t<std::is_floating_point<U>::value, bool> operator==(const U& o) const std::enable_if_t<std::is_floating_point<U>::value, bool> operator==(
noexcept { const U& o) const noexcept {
return operator double() == o;
}
template <class U>
std::enable_if_t<
(!std::is_arithmetic<U>::value && std::is_convertible<U, double>::value), bool>
operator<(const U& o) const noexcept {
return operator double() < o;
}
template <class U>
std::enable_if_t<
(!std::is_arithmetic<U>::value && std::is_convertible<U, double>::value), bool>
operator>(const U& o) const noexcept {
return operator double() > o;
}
template <class U>
std::enable_if_t<
(!std::is_arithmetic<U>::value && std::is_convertible<U, double>::value), bool>
operator==(const U& o) const noexcept {
return operator double() == o; return operator double() == o;
} }

View File

@ -11,20 +11,25 @@
#include <boost/mp11/algorithm.hpp> #include <boost/mp11/algorithm.hpp>
#include <boost/mp11/list.hpp> #include <boost/mp11/list.hpp>
#include <boost/mp11/utility.hpp> #include <boost/mp11/utility.hpp>
#include <type_traits>
namespace boost { namespace boost {
namespace histogram { namespace histogram {
namespace detail { namespace detail {
template <class T, class U> template <class T, class U>
using if_not_same_and_has_eq = using if_not_same = std::enable_if_t<(!std::is_same<T, U>::value), bool>;
std::enable_if_t<(!std::is_same<T, U>::value && has_method_eq<T, U>::value), bool>;
// template <class T, class U>
// using if_not_same_and_has_eq =
// std::enable_if_t<(!std::is_same<T, U>::value && !has_method_eq<T, U>::value),
// bool>;
// totally_ordered is for types with a <= b == !(a > b) [floats with NaN violate this] // totally_ordered is for types with a <= b == !(a > b) [floats with NaN violate this]
// Derived must implement <,== for symmetric form and <,>,== for non-symmetric. // Derived must implement <,== for symmetric form and <,>,== for non-symmetric.
// partially_ordered is for types with a <= b == a < b || a == b [for floats with NaN] // partially_ordered is for types with a <= b == a < b || a == b [for floats with NaN]
// Derived must implement <,== for the symmetric form and <,>,== for non-symmetric. // Derived must implement <,== for symmetric form and <,>,== for non-symmetric.
template <class T, class U> template <class T, class U>
struct mirrored { struct mirrored {
@ -34,32 +39,33 @@ struct mirrored {
friend bool operator<=(const U& a, const T& b) noexcept { return b >= a; } friend bool operator<=(const U& a, const T& b) noexcept { return b >= a; }
friend bool operator>=(const U& a, const T& b) noexcept { return b <= a; } friend bool operator>=(const U& a, const T& b) noexcept { return b <= a; }
friend bool operator!=(const U& a, const T& b) noexcept { return b != a; } friend bool operator!=(const U& a, const T& b) noexcept { return b != a; }
}; }; // namespace histogram
template <class T> template <class T>
struct mirrored<T, void> { struct mirrored<T, void> {
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator<(const U& a, const T& b) noexcept { friend if_not_same<T, U> operator<(const U& a, const T& b) noexcept {
return b > a; return b > a;
} }
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator>(const U& a, const T& b) noexcept { friend if_not_same<T, U> operator>(const U& a, const T& b) noexcept {
return b < a; return b < a;
} }
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator==(const U& a, const T& b) noexcept { friend std::enable_if_t<(!has_method_eq<U, T>::value), bool> operator==(
return b == a; const U& a, const T& b) noexcept {
return b.operator==(a);
} }
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator<=(const U& a, const T& b) noexcept { friend if_not_same<T, U> operator<=(const U& a, const T& b) noexcept {
return b >= a; return b >= a;
} }
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator>=(const U& a, const T& b) noexcept { friend if_not_same<T, U> operator>=(const U& a, const T& b) noexcept {
return b <= a; return b <= a;
} }
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator!=(const U& a, const T& b) noexcept { friend if_not_same<T, U> operator!=(const U& a, const T& b) noexcept {
return b != a; return b != a;
} }
}; };
@ -77,7 +83,7 @@ struct equality {
template <class T> template <class T>
struct equality<T, void> { struct equality<T, void> {
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator!=(const T& a, const U& b) noexcept { friend if_not_same<T, U> operator!=(const T& a, const U& b) noexcept {
return !(a == b); return !(a == b);
} }
}; };
@ -91,11 +97,11 @@ struct totally_ordered_impl : equality<T, U>, mirrored<T, U> {
template <class T> template <class T>
struct totally_ordered_impl<T, void> : equality<T, void>, mirrored<T, void> { struct totally_ordered_impl<T, void> : equality<T, void>, mirrored<T, void> {
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator<=(const T& a, const U& b) noexcept { friend if_not_same<T, U> operator<=(const T& a, const U& b) noexcept {
return !(a > b); return !(a > b);
} }
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator>=(const T& a, const U& b) noexcept { friend if_not_same<T, U> operator>=(const T& a, const U& b) noexcept {
return !(a < b); return !(a < b);
} }
}; };
@ -114,11 +120,11 @@ struct partially_ordered_impl : equality<T, U>, mirrored<T, U> {
template <class T> template <class T>
struct partially_ordered_impl<T, void> : equality<T, void>, mirrored<T, void> { struct partially_ordered_impl<T, void> : equality<T, void>, mirrored<T, void> {
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator<=(const T& a, const U& b) noexcept { friend if_not_same<T, U> operator<=(const T& a, const U& b) noexcept {
return a < b || a == b; return a < b || a == b;
} }
template <class U> template <class U>
friend if_not_same_and_has_eq<T, U> operator>=(const T& a, const U& b) noexcept { friend if_not_same<T, U> operator>=(const T& a, const U& b) noexcept {
return a > b || a == b; return a > b || a == b;
} }
}; };

View File

@ -120,3 +120,4 @@ endif()
# LINK_LIBRARIES Boost::serialization) # LINK_LIBRARIES Boost::serialization)
boost_test(TYPE run SOURCES deduction_guides_test.cpp COMPILE_FEATURES cxx_std_17) boost_test(TYPE run SOURCES deduction_guides_test.cpp COMPILE_FEATURES cxx_std_17)
boost_test(TYPE run SOURCES issue_290_test.cpp COMPILE_FEATURES cxx_std_17)

View File

@ -92,8 +92,9 @@ alias cxx14 :
; ;
alias cxx17 : alias cxx17 :
[ run deduction_guides_test.cpp ] : [ run deduction_guides_test.cpp ]
[ requires cpp_deduction_guides ] [ compile issue_290_test.cpp ]
: [ requires cpp_deduction_guides ]
; ;
# check that useful error messages are produced when library is used incorrectly # check that useful error messages are produced when library is used incorrectly

View File

@ -24,8 +24,6 @@ namespace tr = axis::transform;
// tests requires a C++17 compatible compiler // tests requires a C++17 compatible compiler
#define TEST BOOST_TEST_TRAIT_SAME
int main() { int main() {
using axis::null_type; using axis::null_type;

18
test/issue_290_test.cpp Normal file
View File

@ -0,0 +1,18 @@
// Copyright 2020 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
// - bug only appears on cxxstd=17 or higher and only in gcc
// - reported in issue: https://github.com/boostorg/histogram/issues/290
// - originally caused by rank struct in boost/type_traits/rank.hpp,
// which we emulate here to avoid the dependency on boost.type_traits
// Original: #include <boost/type_traits/rank.hpp>
template <class T>
struct rank;
#include <boost/histogram/axis/traits.hpp>
int main() { return 0; }