Home > OS >  SFINAE User-Defined-Conversion Operator
SFINAE User-Defined-Conversion Operator

Time:02-18

I'm trying to do a templated user-defined-conversion that uses design by introspection.

In C 20 I can do the following:

    template<typename T>
    operator T() const
    {
        if constexpr( requires { PFMeta<T>::from_cursor(*this); } )
        {
            return PFMeta<T>::from_cursor(*this);
        }
        else
        {
            T x;
            std::memcpy(&x, &data, sizeof(x));
            return x;
        }
    }

i.e. if PFMeta<T>::from_cursor(const struct PFCursor&) is defined then use it else do a memcpy. (longer example here in godbolt: https://godbolt.org/z/abed1vsK3)

I love this approach but unfortunately this library will need to work on C 17 too.

So I've been trying SFINAE as an alternative to the concepts but it's very tricky. I finally managed to get something similar but with a templated method as rather than the user-defined-conversion operator itself:

template<typename T, typename = void>
struct has_from_cursor : std::false_type { };

template<typename T>
struct has_from_cursor<T, decltype( PFMeta<T>::from_cursor(std::declval<struct PFCursor>()), void() ) > : std::true_type { };

// ...

    template<class T>
    std::enable_if_t<has_from_cursor<T>::value, T> as() const
    {
        return PFMeta<T>::from_cursor(*this);
    }

    template<class T>
    std::enable_if_t<!has_from_cursor<T>::value, T> as() const
    {
        T x;
        std::memcpy(&x, &data, sizeof(x));
        return x;
    }

I tried the following which compiles but does not work (I can't cast with it):

    template<typename T>
    operator std::enable_if_t<has_from_cursor<T>::value, T>() const
    {
        return PFMeta<T>::from_cursor(*this);
    }

    template<typename T>
    operator std::enable_if_t<!has_from_cursor<T>::value, T>() const
    {
        T x;
        std::memcpy(&x, &data, sizeof(x));
        return x;
    }

Longer example here: https://godbolt.org/z/5r9Mbo18h

So two questions:

  • Can I do effectively what I can do with concepts with just SFINAE in C 17 for the user defined conversion operator?
  • Is there a simpler way to do the as approach than the type traits & enable_if? Ideally I'd like to do something like define the default templated method and then have a specialisation to be preferred if the condition is there (i.e. there is a meta class with a static member function defined).

Thanks!

CodePudding user response:

Can I do effectively what I can do with concepts with just SFINAE in C 17 for the user defined conversion operator?

Consider that C 17 support if constepr. Given that you've developed a has_from_cursor custom type traits that inherit from std::true_type or from std::false_type, you can use it for if constexp.

I mean (caution: code not tested)

template<typename T>
operator T() const
{ // .............VVVVVVVVVVVVVVVVVVVVVVVVV
    if constexpr( has_from_cursos<T>::value )
    {
        return PFMeta<T>::from_cursor(*this);
    }
    else
    {
        T x;
        std::memcpy(&x, &data, sizeof(x));
        return x;
    }
}

Is there a simpler way to do the as approach than the type traits & enable_if? Ideally I'd like to do something like define the default templated method and then have a specialisation to be preferred if the condition is there (i.e. there is a meta class with a static member function defined).

I suppose you can try tag-dispatching...

I mean something as calling 'as()', from operator T() with an additional int argument

template<typename T>
operator T() const
{ return as<T>(0); } // <-- call as() with a int

where there is a as() specific for from_cursos class enabled, that receive an unused int and is simply SFINAE enabled/disabled through decltype()

template <typename T>  // ........accept an int; best match
decltype(PFMeta<T>::from_cursor(*this)) as (int) const
 { return PFMeta<T>::from_cursor(*this); }

and a generic as(), receiving a long

template <typename T>
T as (long) const // <-- accept a long; worst match
 {
   T x;
   std::memcpy(&x, &data, sizeof(x));
   return x;
 }

The trick is the unused argument: a int.

When the specialized as() is enabled, if preferred because accept a int so is a better match.

When the specialized as() is disabled, remain the generic as() as better-than-nothig match.

  • Related