0

Given you ONLY have as a tool a SFINAE class, that accepts an index and yields std::true_type if it is in range, how could one find the limit? This are the exact requiremants, NOTHING else can be used.

This is an example signature for a struct. L here is for testing. In the real struct there will be typename T as a second parameter, but it is irrelevant to the problem.

template <size_t I, size_t L = 6000>
struct is_in_range : std::integral_constant<bool, (I < L)> {};

For example: you have a range of 6000 elements. is_in_range<2>() will be true, is_in_range<6001>() will be false. I need to find the limit, which is in this case 6000.

I know that you could basically refer to the binary search, but the thing is, it has to be as optimal as possible and generate as little instantiations as possible.

For the template part I see 2 ways:

  • template constexpr recursive function (a bad one, bloated code, but I can do that)
  • SFINAE (too complex for me)

I did not want to overcomplicate the question and constrained it only to the scope of the actual problem (sub-problem). The following is not the part of the question, but rather an insight for the reasons of limitations.

This is not a part of the question and is not subjected for discussion since the problem of reflection in C++ is too complex.

In order to deduce at compile time a member of a structure I use aggregate initialization.

template <typename Allowed>
struct explicitly_convertible
{
   operator Allowed();
}

struct pod
{
   int a;
   int arr[46];
   char c;
   float arr2[38];
};

Implementation does the following:

// checks if this is well-formed to deduce the type of an element
   decltype(pod{{explicitly_convertible<int>()}});

So, as you see, the only way to deduce an array size, is to probe the initialization of T with N elements while aggregate initializating.

In C++ a function or a method can't return an array, so this is not acceptable:

template <typename T, size_t N>
struct non_acceptable
{
   operator T[N]();
}

Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • 2
    I don't see any SFINAE here. – Evg May 25 '21 at 09:02
  • 2
    what do you mean by "find the limit"? – jrok May 25 '21 at 09:03
  • @Evg this is just a stub function. The real one will have default specialization for false and a speficific for `true` – Sergey Kolesnik May 25 '21 at 09:03
  • 2
    Do you control `is_in_range`? It'd be easier to add `static constexpr auto UPPER_LIMIT = L;` – StoryTeller - Unslander Monica May 25 '21 at 09:03
  • @jrok I ve added clarification in example – Sergey Kolesnik May 25 '21 at 09:05
  • @StoryTeller-UnslanderMonica I have updated the question. `UPPER_LIMIT` is irrelevant here and is just for the sake of testing – Sergey Kolesnik May 25 '21 at 09:08
  • And what do you mean by bloated code? If the result of binary search is used in the constant expression, most likely the only think you'll have in the binary file is just `6000` constant. – Evg May 25 '21 at 09:11
  • 1
    Sounds to me like an XY problem. Why would the limit L be unknown or not trivially deducible? – Jodocus May 25 '21 at 09:13
  • 1
    C++11 constexpr functions are very limited, C++14 allows simpler code without recursion. – Jarod42 May 25 '21 at 09:13
  • @Evg actually, it will be, but only with optimization. In case you build with debug, MinGW, for instance, will fail to link. This is kind of an XY question (until it isn't, because compilation time also matters), and there is *no solution*. – Sergey Kolesnik May 25 '21 at 09:13
  • @Jarod42 indeed it is limited. But I am *also limited* by the use of C++11, hence the tag in the question ;) – Sergey Kolesnik May 25 '21 at 09:14
  • [`std::partition_point`](https://en.cppreference.com/w/cpp/algorithm/partition_point) is constexpr in C++20 :) – Jarod42 May 25 '21 at 09:15
  • Fail to link? Could you show an example? Sounds like a compiler bug. – Evg May 25 '21 at 09:18
  • @Evg help yourself: https://digitalkarabela.com/mingw-w64-how-to-fix-file-too-big-too-many-sections/ https://stackoverflow.com/questions/65840449/mingw-does-not-recognise-pragma-gcc-optimize – Sergey Kolesnik May 25 '21 at 09:20
  • 1
    _" This is kind of an XY question (until it isn't, because compilation time also matters), and there is no solution"_ That doesn't give more insights. You are asking us to fix your solution to a problem(, which might not be a good solution). We are asking: *what is the actual problem you are trying to fix (with your solution)*. *Why* do you want to check a range in the template argument? Why do you want to check a range *at all*? – JHBonarius May 25 '21 at 09:21
  • I would say get a `constexpr std::array arr = {{is_in_range {}...}};`, then a manual `partition_point` (or even a "sum" or linear search). – Jarod42 May 25 '21 at 09:22
  • 1
    _"But the real problem is TOO COMPLEX to describe. And since SO rules encourage to separate questions, this is the result."_ <-- sorry, but your are misinterpreting the guidelines. You should definitely explain more (not everything, but definitely more) on the problem. XY questions are not what you want, as (like I said) were fixing your solution, not the actual problem. – JHBonarius May 25 '21 at 09:24
  • @JHBonarius you know, I spent a lot of time on SO, and I always expect a lot of questions. So I **have** done **a lot** of research on my problem before asking **this particular question**. So **it is** the problem in the question that I am trying to solve. – Sergey Kolesnik May 25 '21 at 09:26
  • @Jarod42 I have updated the question. The only thing can be used is a given sfinae struct, unfortunately – Sergey Kolesnik May 25 '21 at 09:27
  • *"template constexpr recursive function (a bad one, bloated code, but I can do that)"*. It seems it fixes your issue. You have log of instantiation. – Jarod42 May 25 '21 at 09:29
  • Where is that limit stored? In what form? Can't you just make a template that pulls this constant out of the template instantiation? – Timo May 25 '21 at 09:30
  • log2 instantiations is the best you can hope for here... I think your suggested 2 ways are the only 2 ways (if we replace SFINAE with a broader term TMP). – rustyx May 25 '21 at 09:36
  • @JHBonarius I added an explanation of an actual problem. – Sergey Kolesnik May 25 '21 at 09:37
  • @Timo I've added an explanation for the limitations. The fact that people assume that I haven't done any research is very frustrating. – Sergey Kolesnik May 25 '21 at 09:41
  • 2
    BTW the actual problem reminds me of magic_get (aka boost.pfr). – rustyx May 25 '21 at 09:46
  • @rustyx this is for my header-only reflection library https://github.com/ElDesalmado/pod_reflection – Sergey Kolesnik May 25 '21 at 10:12
  • @Jarod42 i've looked into `std::partition_point`. It can only be used if you provide an upper bound. It can potentially be less efficient than just linear probing. Take for instance an array of 4 elements. If you *guess* that the upper bound is 10000, you will get more instantiations. – Sergey Kolesnik May 25 '21 at 14:29
  • @Jarod42 I think the solution would be something like https://stackoverflow.com/questions/6547/two-marbles-and-a-100-story-building – Sergey Kolesnik May 25 '21 at 14:34
  • Oh, you don't know the upper limit, so if you don't know the distribution, then you have to use `numeric_limits::max`. The linked question has upper limit, and canot use binary search, whereas you can. If you know something about distribution of the result, then yes, other strategy might be adopted. – Jarod42 May 25 '21 at 15:43
  • @Jarod42 I can assume the upper limit and more precisely than a numeric limit. The size of an array member is never greater than the size of a containing structure. But the re may be corner cases, like a struct with several arrays. – Sergey Kolesnik May 25 '21 at 15:59

1 Answers1

0

Returning to this problem, I decided to implement a naive solution. The algorithm "probes" the size for 2 (left and right) boundaries (pseudo):

bound<left, right, delta>:

if right.valid() 
    return bound<right, right + delta * 2, delta * 2>;

if right - left == 1
    return left;

return bound<right - delta, right - delta / 2, delta / 2>;

The actual soultion using recursive specialization: https://godbolt.org/z/9djsdjKE4

#include <cstddef>
#include <type_traits>

template <template <size_t> class OnFound,
    template <size_t> class UnaryPred,
    typename Left, 
    typename Right, 
    size_t Delta = 1>
struct _find_array_size;

template <size_t Index, template <size_t> class UnaryPred, bool = UnaryPred<Index>::value>
struct _probe_size;

template <template <size_t> class OnFound, 
        template <size_t> class UnaryPred,
        size_t Left>
struct _find_array_size<OnFound, UnaryPred, 
                    _probe_size<Left, UnaryPred, true>, 
                    _probe_size<Left + 1, UnaryPred, false>> 
{
    using type = OnFound<Left>;
    constexpr static size_t value = Left;
};

template <template <size_t> class OnFound, 
        template <size_t> class UnaryPred,
        size_t Left,
        size_t Right,
        bool BoolRight,
        size_t Delta>
struct _find_array_size<OnFound, UnaryPred, 
                    _probe_size<Left, UnaryPred, true>, 
                    _probe_size<Right, UnaryPred, BoolRight>,
                    Delta> : std::conditional_t<BoolRight,
                                _find_array_size<OnFound, UnaryPred, _probe_size<Right, UnaryPred>, _probe_size<Right + Delta * 2, UnaryPred>, Delta * 2>,
                                _find_array_size<OnFound, UnaryPred, _probe_size<Right - Delta, UnaryPred>, _probe_size<Right - Delta / 2, UnaryPred>, Delta / 2>>
{
};

template <template <size_t> class OnFound,
        template <size_t> class UnaryPred>
struct find_array_size
{
    constexpr static size_t value = _find_array_size<OnFound, UnaryPred, _probe_size<1, UnaryPred>, _probe_size<2, UnaryPred>>::value;
};

// Testing
template <size_t ArraySize>
struct in_bounds
{
    template <size_t Size>
    using predicate = std::integral_constant<bool, (Size <= ArraySize)>;
};

#include <iostream>

template <size_t> 
struct t_noop;

int main(){

    std::cout << std::boolalpha;
    std::cout << in_bounds<46>::template predicate<45>{} << '\n';
    std::cout << in_bounds<46>::template predicate<46>{} << '\n';
    std::cout << find_array_size<t_noop, in_bounds<1>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<2>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<3>::predicate>::value << '\n';
    
    std::cout << find_array_size<t_noop, in_bounds<4>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<8>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<15>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<16>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<23>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<42>::predicate>::value << '\n';
    std::cout << find_array_size<t_noop, in_bounds<108>::predicate>::value << '\n';

    return 0;
}

Output:

true
true
1
2
3
4
8
15
16
23
42
108

It works though I am not sure if it could be made more optimal from both algorithmic and compilation perspective.

Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28