0

I have a class template

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" has to be a structure, created in compile-time, containing all fibonacci numbers of type U, from 1 to maximal possible fibonacci number smaller than max_U. Since I don't know what the type U is (I only know that it's arithmetic), I have to somehow check how many numbers I can generate. I tried many different approaches, but none of them worked.

For example, I tried doing something like this:

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);

But I get an error: "fatal error: recursive template instantiation exceeded maximum depth of 256".

Jecke
  • 239
  • 2
  • 13
  • This looks weird: `if (Fib() <= Fib())` – YSC Nov 29 '16 at 17:22
  • Well, that's where I get an error. But 1. I don't know why; 2. I don't know how to change it – Jecke Nov 29 '16 at 17:31
  • I'm not sure what you expect the set to contain. If input is {1,3,4}, the set should contain {Fib_1, Fib_3 and Fib_4}? – YSC Nov 29 '16 at 17:40
  • No, there is no input, since the set has to be made during compilation. I expect it to contain all fibonacci numbers (1, 2, 3, 5, 8, 13 ... max fibonacci number that fits in U). – Jecke Nov 29 '16 at 17:48
  • Out of curiosity, in `make_fibonacci_set()`, is `n` supposed to be a starting point or an upper limit? If it's a starting point, it should probably default to `0`. – Justin Time - Reinstate Monica Nov 29 '16 at 18:20
  • Also note that n as a parameter cannot be a template parameter. – Jonathan Nov 29 '16 at 18:24
  • n is a starting point. I wanted to initialize `const std::set fibonacci = make_fibonacci_set(1, 1);` (not 0, because f0 = f1 = 1) – Jecke Nov 29 '16 at 18:28
  • @Jonathan I see, that explains why it didn't work... But when I changed it to be part of a template, I get another error (see edited question) – Jecke Nov 29 '16 at 18:32
  • 1
    What happens when you call `Fib()` and `N == 1`? Hint: It's not what you think, because of how `unsigned` types work. – Justin Time - Reinstate Monica Nov 29 '16 at 18:37
  • horrible complexity. I hope it's just an exercise... btw Justin Time is correct, it should be `if (N <= 1) return 1;` – UmNyobe Nov 29 '16 at 18:40
  • @JustinTime You are right... but I changed it to `N <= 1` and there is still the same problem... I start with `const std::set fibonacci = make_fibonacci_set(1);` – Jecke Nov 29 '16 at 18:49
  • Just thought of something: `std::set` stores _unique_ elements. Since the Fibonacci sequence starts with two `1`s, a set won't properly store it. – Justin Time - Reinstate Monica Nov 29 '16 at 22:50

1 Answers1

2

Due to a quirk of the language, Fib() and make_fibonacci_set(), as written, will have infinite recursion (specifically, to my understanding, the problem is that while only one branch is chosen, both are evaluated; this causes the compiler to instantiate the templates required by the recursive branch, even when the other is chosen, generating infinite instantiations). To my understanding, constexpr if would resolve this nicely; however, I don't currently have access to any compilers that support it, so this answer will instead rework the former to rely on a helper (so introspection can be performed, and to aid in making a fully compile-time container class), and use SFINAE to break the latter into two distinct functions (to hide each one's return statement from the other).

First, before we get to the actual code, we'll want a helper macro if MSVC compatibility is desired, due to its (as of Nov.29, 2016) incomplete support of expression 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

And now, the code itself. First, Fib()'s helper.

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

This allows us to define Fib() trivially, and provide a convenient means of introspection.

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>();
};

And now, for make_fibonacci_set(). I changed the way this one works a bit; specifically, I made it a wrapper for another function called make_fibonacci_seq(), which is a more generic version that works for any valid container.

// 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>();
}

This can cleanly assign the sequence to a std::set, or to other types (such as 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>();
};

If you want fibonacci to be created at compile time, however, it has to be a LiteralType, a type that can be created at compile time. std::set<T> is not a LiteralType, hence it can't be used for a compile-time Fibonacci sequence. Therefore, if you want to guarantee that it'll be constructed at compile time, you'll want your class to use a compile-time constructible container, such as std::array. Conveniently, make_fibonacci_seq() there lets you specify the container, so...

// 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>();

See it in action here (original link here).

  • Faaar too many template instantiations just to get a few Fibonacci numbers, really :-). I'd try to use more `constexpr`, especially if C++14 is an option. Also, the method for finding the largest representable number assumes there are no integral types larger than ULL, which may not be the case. – bogdan Nov 30 '16 at 10:51
  • @JustinTime Thanks a lot! I won't pretend I understand all of that, but it works :) I tried to slightly simplify it for my needs (and to better understand it) and that's what I ended up with: http://pastebin.com/UWPnE2Ff Could you tell me if that solution is correct, and if not - why not? – Jecke Nov 30 '16 at 11:13
  • @bogdan how would you simplify it using constexpr? And what would you change, so that it would also work if typename U is bigger than unsigned long long? – Jecke Nov 30 '16 at 12:58
  • 2
    @Jecke A quick solution requiring a compiler with reasonable support for C++14 `constexpr` [could look like this](http://melpon.org/wandbox/permlink/4jziAcTwPhnkUuXP). It works on relatively recent Clang and GCC versions, and also on MSVC 2017 RC. (Note that I don't think it makes sense to allow floating point types.) Of course, this is only one variation of many. For example, you could build that `std::array` iteratively in a `constexpr` function, but that would require some C++17 support in the standard library that I think only GCC 7 has currently. – bogdan Nov 30 '16 at 13:57
  • I see, that is, indeed, significantly simple. But how do you put that in a template class (`template class A`)? I tried `static constexpr auto fibonacci = make_fibonacci_sequence();` and `static constexpr auto fibonacci = make_fib_hlp(1, 1, std::make_index_sequence(1, 1)>{});` but both give me error " undefined reference to `A<...arguments....>::fibonacci'" – Jecke Nov 30 '16 at 16:02
  • 2
    @Jecke If you're using that variable in a way that requires it to have a definition, you need to provide one out-of-class (the in-class one is only a declaration in C++14 and before). Given that the two declarations need to specify the same type (and it can't be `auto`), [here's one way to do it](http://melpon.org/wandbox/permlink/FrVHWmzx3EIKFcEX). This is no longer a problem in C++17, due to the introduction of the notion of *inline variables*. – bogdan Nov 30 '16 at 17:50
  • 2
    @Jecke However, note that each of `A` and `A` will have its own copy of that static member, even if they're identical. I don't think the linker will be able to fold those sections into one, unless the compiler can somehow prove that the code doesn't rely on their addresses being different, or you indicate that through some compiler-specific mechanism. That's not something I'd want to depend upon, so I'd separate that variable into a construct that only depends on `U`: a standalone variable template, or a base of `A` of the form `B`, something like that. – bogdan Nov 30 '16 at 18:06
  • Thanks a lot! I know, I share your concerns (several copies of identical array, if I understand correctly), but unfortunately it's not up to me - I have to create (at compile time) a public field fibonacci. Could you please explain what exactly does the `return {{n1, n2, ((void)Is, n2 += n1, n1 = n2 - n1, n2)...}};` line do? – Jecke Nov 30 '16 at 18:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/129466/discussion-between-bogdan-and-jecke). – bogdan Nov 30 '16 at 18:58
  • 2
    @bogdan That's a lot cleaner than my code. Honestly, I'd suggest posting it as a separate answer, instead of hiding it in a comment on my answer. I still have more to learn about `constexpr` and which library members can be used at compile time, but it looks like you've got a pretty good handle on it. – Justin Time - Reinstate Monica Nov 30 '16 at 23:17
  • 1
    Also, I completely forgot about the possibility of there being extended integer types. I'll change that ULL to `uintmax_t`. – Justin Time - Reinstate Monica Nov 30 '16 at 23:17
  • @Jecke You're welcome. And yeah, it looks like it mostly works, except it doesn't propagate the overflow indicator. For example, the largest Fibonacci number storeable in an `int` is `1836311903`; `Fib::value - 1>()` will return that, and `Fib::value>()` will return `4`. However, `Fib::value + 1>()` will be `1836311907`, `Fib::value + 2>()` will be `1836311911`, and `Fib::value + 2>()` will be `4` again. – Justin Time - Reinstate Monica Nov 30 '16 at 23:38
  • That's what the `|| Fib::val == FibOverflowIndicator` in my implementation is intended to catch; when calculating any given Fibonacci number _n_, if the previous number is `FibOverflowIndicator`, it guarantees that _n_ will be, too. – Justin Time - Reinstate Monica Nov 30 '16 at 23:38
  • @JustinTime I don't see my code as cleaner; terser, yes, but I wouldn't nominate that pack expansion for the cleanliness award... The trouble with `uintmax_t` is that it's pretty much pegged at 64 bits on common platforms, for reasons of ABI compatibility. So Clang and GCC offer `__int128` as an extension, not as an extended integer type in the standard sense, to avoid changing `uintmax_t` to fit it (yes, this came as a bit of a surprise to me too). They do support `numeric_limits` and other type traits on it though (GCC / libstdc++ only in GNU mode, unfortunately). – bogdan Dec 01 '16 at 17:18
  • @bogdan Good point. Is there a way to detect whether a compiler supports `__int128` or equivalent, such as a macro or something that'll be defined if it exists? I know MSVC doesn't support it (yet?), and apparently ICC doesn't either, so if I'm going to change the answer to allow it where possible, I want to do it in a way that won't break on some compilers. – Justin Time - Reinstate Monica Dec 01 '16 at 17:53
  • @JustinTime I think the macro `__SIZEOF_INT128__` is defined (to `16`) for GCC and Clang if `__int128` is supported, but I haven't tested it. – bogdan Dec 01 '16 at 18:06
  • @bogdan Just checked with Coliru, and it works for both; GCC gives a `-Wpedantic` warning about `__int128` not being supported by ISO C++, though. Updating the answer now. – Justin Time - Reinstate Monica Dec 03 '16 at 22:21
  • Note that as of C++20, `is_literal_type` will need to be replaced with another trait that is capable of detecting whether a type can be constructed at compile time. – Justin Time - Reinstate Monica Jul 16 '19 at 20:56