2

I am designing a "dereferencer" class, for fun.

I wrote some structs and aliass :

template <class _T>
using deref_type = decltype(*std::declval<_T>());

template <class _T, class _SFINAE>
struct is_derefable : std::false_type {};

template <class _T>
struct is_derefable< _T, deref_type<_T> > : std::true_type
{
    using return_type = deref_type<_T>;
};

template<class _T>
using check_derefable = is_derefable<T, deref_type<T>>;

and let's say that there is a variable with type T = std::vector<int**>::iterator, which is the iterator dereferenced into a level-2 pointer, thus has a 3-level dereferenceability.

Here, I want to know the maximum level of "dereferenceability" of an arbitrary type T, at the compile-time.

std::cout << deref_level<std::vector<int**>::iterator>::max << std::endl; // this should prints 3

I thought that it would be way similar to generating a sequence at the compile-time: Template tuple - calling a function on each element , but I can't draw a concrete picture of it.

Here are what I've tried:

template<class _TF, class _T>
struct derefability {};

template<int _N, class _derefability>
struct deref_level;

template<int _N, class _T>
struct deref_level<_N, derefability<std::false_type, _T>>
{
    static const int max = _N;
};

template<int _N, class _T>
struct deref_level<_N, derefability<std::true_type, _T>> : 
    deref_level<_N + 1, derefability<typename check_derefable<deref_type<_T>>::type, deref_type<_T>>>{};

deref_level<0, derefability<check_derefable<T>::type, T>::max;

but it does not work...(compiler says that max is not a member of tje class) What went wrong?

Quentin
  • 62,093
  • 7
  • 131
  • 191
Moon
  • 111
  • 7
  • 2
    Just so you know, identifiers that begin with an underscore followed by an uppercase letter (`_T`, `_N`, `_SFINAE`, etc.) are reserved to the implementation for *any use*. It's a habit worth breaking. – StoryTeller - Unslander Monica Sep 08 '20 at 09:10
  • 1
    "Does not work" is not specific enough. Was there a compilation error? Any other error? You might want to read this: [mcve]. – anatolyg Sep 08 '20 at 09:15
  • @StoryTeller - Unslander Monica Thanks for your advice, but I don't get what "reserved to the implementation for *any use*" means. Can you write some examples of that usage for me, please? – Moon Sep 08 '20 at 09:16
  • 1
    @Moon See [here](https://stackoverflow.com/q/228783/509868). – anatolyg Sep 08 '20 at 09:17
  • 1
    The implementation is your compiler and system headers. The C++ standard says user programs can't use such identifiers, only the implementation. You aren't **allowed** to use them yourself, under pain of undefined behavior. I can't very well show you an example of something that shouldn't be used. – StoryTeller - Unslander Monica Sep 08 '20 at 09:18

3 Answers3

3

Here is a recursive implementation using SFINAE directly:

template <class T, class = void>
struct deref_level {
    enum : std::size_t { value = 0 };
};

template <class T>
struct deref_level<T, decltype(void(*std::declval<T const &>()))> {
    enum : std::size_t { value = deref_level<decltype(*std::declval<T const &>())>::value + 1 };
};

See it live on Wandbox

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • I have another question, why should I use 'void'-casting wrapping 'decltype'? Is it a "safety net" for the case when deduction failed? If then, `void(error-type)` is cast into `void`? – Moon Sep 09 '20 at 01:02
  • Plus, is there any other possible solution that using `static const int` type for `max_level`, rather than `enum`? – Moon Sep 09 '20 at 01:04
  • Oh, I've only tested it for several types, but not built it. When I build the codes in VS2013, an error occurs : `error C1001: An internal error has occurred in the compiler.` – Moon Sep 09 '20 at 01:31
  • I found a way to solve it. Just use another struct for checking SFINAE and use it. I added the codes to the edited answer of @Quentin. – Moon Sep 09 '20 at 01:57
  • I apologize. The codes @Quentin wrote do not work on my VS2013. When the original Quentin code built, I get a message `error C1001: An internal error has occurred in the compiler`. For the stuct-using SFINAE checked version, it was successfully compiled for nothing inside the `deref_level` struct, but after I put `enum ` code in it then I get a message `error C2100: illegal indirection`. – Moon Sep 09 '20 at 05:22
  • @Moon the cast to `void` is just because the final type must be the same as the default type argument for the specialization to be picked up. I have avoided any variant of `static` member variables for `value`, as I do not remember the C+11 rules around their definition (particularly with ODR). I'm currently reviewing your edit, but IIUC it's not fully functional because VS2013 still freaks out when you actually put in `value`? – Quentin Sep 09 '20 at 08:27
1

I don't know what went wrong with your template example, but here's an implementation using a recursive consteval function:

#include <type_traits>

template<typename T, int N = 0>
consteval int deref_level()
{
    if constexpr (std::is_pointer<T>::value) {
        typedef typename std::remove_pointer<T>::type U;
        return deref_level<U, N + 1>();
    } else {
        return N;
    }
}

int main() {
    return deref_level<int****>(); // Returns 4
}
Tharwen
  • 3,057
  • 2
  • 24
  • 36
  • 1
    Beware that this is C++20, but the question is tagged C++11. It is also restricted to pointers, but OP wants to handle any dereferenceable type such as iterators. – Quentin Sep 08 '20 at 09:20
  • 1
    @Quentin Ah, I didn't see that – Tharwen Sep 08 '20 at 09:22
1

After a few days of work, I was able to write code that works without causing an error in MSVC13.

First of all, I needed a robust module to check the dereferenceability of the type.

Since the struct-level SFINAE check fails, I took another method that deduces the return type from overloaded functions with auto->decltype expression, based on the answer: link

template<class T>
struct is_dereferenceable
{
private:
    template<class _type>
    struct dereferenceable : std::true_type
    {
        using return_type = _type;
    };

    struct illegal_indirection : std::false_type
    {
        using return_type = void*;
    };

    template<class _type>
    static auto dereference(int)->dereferenceable<
        decltype(*std::declval<_type>())>;

    template<class>
    static auto dereference(bool)->illegal_indirection;

    using dereferenced_result = decltype(dereference<T>(0));

public:
    using return_type = typename dereferenced_result::return_type;
    static const bool value = dereferenced_result::value;
};

Now I have a robust dereferenceability-checker, the remaining part becomes far easy.

template< class T,
    class D = typename is_dereferenceable<T>::return_type >
struct dereferenceability;

template< class T >
struct dereferenceability<T, void*>
{
    using level = std::integral_constant<int, 0>;
};

template< class T, class D >
struct dereferenceability<T, D&>
{
    using level = std::integral_constant<int, dereferenceability<D>::level::value + 1>;
};

int main()
{
    static_assert(dereferenceability<int>::level::value == 0, "something went wrong");
    static_assert(dereferenceability<int****>::iterator>::level::value == 4, "something went wrong");
    return 0;
}

I've tested codes above in Visual Studio 2013, and no error occured.

Moon
  • 111
  • 7