[英]How to create compile-time templatized set/array/vector with fibonacci numbers using templates?
我有一個課堂模板
template<typename U, ...more specialization... > class A {
static_assert(std::is_arithmetic<U>::value, "U type must be arithmetic");
public:
const std::set<U> fibonacci = ???; //May be any structure with iterators, not necessarily set
...more code...
};
“ fibonacci”必須是在編譯時創建的結構,其中包含所有類型為U的斐波那契數 ,從1到小於max_U的最大可能斐波那契數。 由於我不知道類型U是什么(我只知道它是算術的),因此我必須以某種方式檢查可以生成多少個數字。 我嘗試了許多不同的方法,但沒有一個起作用。
例如,我嘗試執行以下操作:
template <typename U, size_t N>
constexpr U Fib() {
if (N <= 1) return 1; //was N < 1 (incorrect)
return Fib<U, N-1>() + Fib<U, N-2>();
}
template <typename U, size_t n, typename ... Args>
constexpr std::set<U> make_fibonacci_set(Args ...Fn) {
if (Fib<U, n>() <= Fib<U, n-1>()) {
return std::set<U> {{Fn...}};
}
else {
return make_fibonacci_set<U, n+1>(Fn..., Fib<U, n>());
}
}
at class A...: const std::set<U> fibonacci = make_fibonacci_set<U, 2>(1);
但是我得到一個錯誤:“致命錯誤:遞歸模板實例超過了最大深度256”。
由於語言的怪異,如所寫, Fib()
和make_fibonacci_set()
將具有無限遞歸(具體來說,據我所知,問題是,雖然只選擇了一個分支,但兩個分支都被求值;這使編譯器實例化遞歸分支所需的模板,即使選擇了另一個,也會生成無限的實例化)。 據我了解, constexpr if
可以很好地解決這個問題; 但是,我目前無法訪問任何支持它的編譯器,因此此答案將改寫前者以依賴於助手(這樣可以進行內省,並有助於制作一個完全編譯時的容器類),並使用SFINAE將后者分為兩個不同的函數(將彼此的return
語句隱藏在另一個函數中)。
首先,在進入實際代碼之前,如果需要MSVC兼容性,我們將需要一個輔助宏,因為它(截至2016年11月29日)不完全支持表達式SFINAE。
// Check for MSVC, enable dummy parameter if we're using it.
#ifdef _MSC_VER
#define MSVC_DUMMY int MSVCDummy = 0
#else // _MSC_VER
#define MSVC_DUMMY
#endif // _MSC_VER
現在,代碼本身。 首先,是Fib()
的助手。
namespace detail {
// Error indicating.
// Use 4 to indicate overflow, since it's not a Fibonacci number.
// Can safely be replaced with any number that isn't in the Fibonacci sequence.
template<typename U>
constexpr U FibOverflowIndicator = 4;
// -----
// Fibonacci sequence.
template<typename U, size_t N>
struct Fib {
private:
static constexpr U getFib();
public:
// Initialised by helper function, so we can indicate when we overflow U's bounds.
static constexpr U val = getFib();
};
// Special cases: 0 and 1.
template<typename U>
struct Fib<U, 0> {
static constexpr U val = 1;
};
template<typename U>
struct Fib<U, 1> {
static constexpr U val = 1;
};
// Initialiser.
template<typename U, size_t N>
constexpr U Fib<U, N>::getFib() {
// Calculate number as largest unsigned type available, to catch potential overflow.
// May emit warnings if type actually is largest_unsigned_t, and the value overflows.
// Check for existence of 128-bit unsigned types, or fall back to uintmax_t if none are available.
// Update with any other platform- or compiler-specific checks and type names as necessary.
// Note: GCC will emit warnings about use of __int128, if -Wpedantic is specified.
#ifdef __SIZEOF_INT128__
using largest_unsigned_t = unsigned __int128;
#else // __SIZEOF_INT128__
using largest_unsigned_t = std::uintmax_t;
#endif // __SIZEOF_INT128__
largest_unsigned_t temp = static_cast<largest_unsigned_t>(Fib<U, N-1>::val) +
Fib<U, N-2>::val;
// Cast number back to actual type, and make sure that:
// 1. It's larger than the previous number.
// 2. We didn't already overflow.
// If we're good, return the number. Otherwise, return overflow indicator.
return ((static_cast<U>(temp) <= Fib<U, N-1>::val) ||
Fib<U, N-1>::val == FibOverflowIndicator<U>
? FibOverflowIndicator<U>
: static_cast<U>(temp));
}
// -----
// Introspection.
template<typename U, size_t N>
constexpr bool isValidFibIndex() {
return Fib<U, N>::val != FibOverflowIndicator<U>;
}
template<typename U, size_t N = 0>
constexpr std::enable_if_t<!isValidFibIndex<U, N + 1>(), U>
greatestStoreableFib(MSVC_DUMMY) {
return Fib<U, N>::val;
}
template<typename U, size_t N = 0>
constexpr std::enable_if_t<isValidFibIndex<U, N + 1>(), U>
greatestStoreableFib() {
return greatestStoreableFib<U, N + 1>();
}
template<typename U, size_t N = 0>
constexpr std::enable_if_t<!isValidFibIndex<U, N + 1>(), size_t>
greatestStoreableFibIndex(MSVC_DUMMY) {
return N;
}
template<typename U, size_t N = 0>
constexpr std::enable_if_t<isValidFibIndex<U, N + 1>(), size_t>
greatestStoreableFibIndex() {
return greatestStoreableFibIndex<U, N + 1>();
}
} // namespace detail
這使我們可以輕松定義Fib()
,並提供一種方便的內省方法。
template<typename U, size_t N>
constexpr U Fib() {
return detail::Fib<U, N>::val;
}
template<typename U>
struct FibLimits {
// The largest Fibonacci number that can be stored in a U.
static constexpr U GreatestStoreableFib = detail::greatestStoreableFib<U>();
// The position, in the Fibonacci sequence, of the largest Fibonacci number that U can store.
// Position is zero-indexed.
static constexpr size_t GreatestStoreableFibIndex = detail::greatestStoreableFibIndex<U>();
// The number of distinct Fibonacci numbers U can store.
static constexpr size_t StoreableFibNumbers = GreatestStoreableFibIndex + 1;
// True if U can store the number at position N in the Fibonacci sequence.
// Starts with 0, as with GreatestStoreableFibIndex.
template<size_t N>
static constexpr bool IsValidIndex = detail::isValidFibIndex<U, N>();
};
現在,對於make_fibonacci_set()
。 我改變了這個工作的方式。 具體來說,我將其包裝為另一個名為make_fibonacci_seq()
函數的包裝,該函數是更通用的版本,適用於任何有效容器。
// Fibonacci number n is too large to fit in U, let's return the sequence.
template<typename U, typename Container, size_t n, U... us>
constexpr std::enable_if_t<Fib<U, n>() == detail::FibOverflowIndicator<U>, Container>
make_fibonacci_seq(MSVC_DUMMY) {
return {{us...}};
}
// Fibonacci number n can fit inside a U, continue.
template<typename U, typename Container, size_t n, U... us>
constexpr std::enable_if_t<Fib<U, n>() != detail::FibOverflowIndicator<U>, Container>
make_fibonacci_seq() {
return make_fibonacci_seq<U, Container, n+1, us..., Fib<U, n>()>();
}
// Wrapper for std::set<U>.
template<typename U, size_t n>
constexpr auto make_fibonacci_set() {
return make_fibonacci_seq<U, std::set<U>, n>();
}
這可以將序列干凈地分配給std::set
或其他類型(例如std::vector
。
template<typename U> class A {
static_assert(std::is_arithmetic<U>::value, "U type must be arithmetic");
public:
// Assign to std::set.
const std::set<U> fibonacci = make_fibonacci_set<U, 0>();
// Assign to any container.
const std::vector<U> fibonacci_v = make_fibonacci_seq<U, std::vector<U>, 0>();
};
但是,如果希望在編譯時創建fibonacci
,則必須為LiteralType
,該類型可以在編譯時創建。 std::set<T>
不是LiteralType
,因此不能用於編譯時Fibonacci序列。 因此,如果要保證在編譯時構造它,則希望您的類使用可編譯時構造的容器,例如std::array
。 方便地, make_fibonacci_seq()
可讓您指定容器,因此...
// Use FibLimits to determine bounds for default container.
template<typename U, typename Container = std::array<U, FibLimits<U>::StoreableFibNumbers>>
class Fibonacci {
static_assert(std::is_arithmetic<U>::value, "U type must be arithmetic.");
static_assert(std::is_literal_type<Container>::value, "Container type must be a LiteralType.");
public:
using container_type = Container;
static constexpr Container fibonacci = make_fibonacci_seq<U, Container, 0>();
};
template<typename U, typename Container>
constexpr Container Fibonacci<U, Container>::fibonacci;
// -----
// Alternative, more robust version.
// Conditionally constexpr Fibonacci container wrapper; Fibonacci will be constexpr if LiteralType container is supplied.
// Use FibLimits to determine bounds for default container.
template<typename U,
typename Container = std::array<U, FibLimits<U>::StoreableFibNumbers>,
bool = std::is_literal_type<Container>::value>
class Fibonacci;
// Container is constexpr.
template<typename U, typename Container>
class Fibonacci<U, Container, true> {
static_assert(std::is_arithmetic<U>::value, "U type must be arithmetic.");
static_assert(std::is_literal_type<Container>::value, "Container type must be a LiteralType.");
public:
using container_type = Container;
static constexpr Container fibonacci = make_fibonacci_seq<U, Container, 0>();
static constexpr bool is_constexpr = true;
};
template<typename U, typename Container>
constexpr Container Fibonacci<U, Container, true>::fibonacci;
// Container isn't constexpr.
template<typename U, typename Container>
class Fibonacci<U, Container, false> {
static_assert(std::is_arithmetic<U>::value, "U type must be arithmetic.");
public:
using container_type = Container;
static const Container fibonacci;
static constexpr bool is_constexpr = false;
};
template<typename U, typename Container>
const Container Fibonacci<U, Container, false>::fibonacci = make_fibonacci_seq<U, Container, 0>();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.