2

I am working on downgrading a project written in C++ 17 to C++ 14. While downgrading, I came across a piece of code involving if constexpr and I wish to convert it to C++ 14 (From what I know, if constexpr is a C++ 17 feature).

Boost's is_detected is used to check if a given type has star operator or get method.

#include <iostream>
#include <boost/type_traits/is_detected.hpp>
#include <type_traits>
#include <boost/optional/optional.hpp>
#include <memory>
#include <typeinfo>

template < template < typename... > typename Operation, typename... Args >
constexpr bool is_detected_v = boost::is_detected< Operation, Args... >::value;

template < typename T >
using has_star_operator = decltype( *std::declval< T >( ) );

template < typename T >
using has_get_method = decltype( std::declval< T >( ).get( ) );

There is a function call deref which is used to dereference types like pointers, arrays, iterators, smart pointers, etc.

template < typename T >
inline constexpr const auto&
deref( const T& value )
{
    if constexpr ( is_detected_v< has_star_operator, T > )
    {
        return deref( *value );
    }
    else if constexpr ( is_detected_v< has_get_method, T > )
    {
        return deref( value.get( ) );
    }
    else
    {
        return value;
    }
}

I tried to form a solution without if constexpr by using std::enable_if as below:

template <typename T>
typename std::enable_if<
    !is_detected_v<has_get_method, T> && is_detected_v<has_star_operator, T>,
    decltype( *std::declval< const T >( ) )>::type
deref(const T& value)
{
    std::cout << "STAR " << typeid(*value).name() << std::endl;
    return *value;
}

template <typename T>
typename std::enable_if<
    is_detected_v<has_get_method, T>, 
    decltype( std::declval< const T >( ).get( ) ) >::type
deref(const T& value)
{
    std::cout << "GET " << typeid(value.get()).name() << std::endl;
    return value.get();
}

template <typename T>
typename std::enable_if<
    !is_detected_v<has_get_method, T> && !is_detected_v<has_star_operator, T>,
    const T>::type
deref(const T& value)
{
    std::cout << "NONE\n";
    return value;
}

int main()
{
    int VALUE = 42;
    boost::optional<int> optional_value = boost::make_optional(VALUE);
    int a = 42;
    int *b = &a;
    const int array[ 4 ] = {VALUE, 0, 0, 0};
    //const auto list = {std::make_unique< int >( VALUE ), std::make_unique< int >( 0 ),
    //                   std::make_unique< int >( 0 )};
    //const auto iterator = list.begin( );
    //std::unique_ptr<int> u = std::make_unique< int >( VALUE );
    std::cout << deref(a) << std::endl;
    std::cout << deref(optional_value) << std::endl;
    std::cout << deref(b) << std::endl;
    std::cout << deref(array) << std::endl;
    //std::cout << deref(iterator) << std::endl;
    //std::cout << deref(u) << std::endl;
}

But, the above fails for cases like iterators and smart pointers where multiple dereference has to be made. For example, for a std::unique_ptr, first p.get() will be called (auto q = p.get()) followed by star operator (*q).

I am a beginner with templates and require some help in this. Please let me know how this can be solved.

I am using GCC 5.4 to compile.

kiner_shah
  • 3,939
  • 7
  • 23
  • 37
  • You may find this [Github repo](https://github.com/bitwizeshift/BackportCpp) interesting. – rawrex May 19 '22 at 05:47
  • @rawrex, thanks for the suggestion, but the table for C++ 17 doesn't mention about `if constexpr` so not sure if it's supported. – kiner_shah May 19 '22 at 05:51
  • @FantasticMrFox, I didn't understand your question properly. Can you elaborate? – kiner_shah May 19 '22 at 06:12
  • @rawrex that repo is only for the standard library not for language features – Alan Birtles May 19 '22 at 07:31
  • @AlanBirtles Yes. Nevertheless my comment was in regards to the *"...downgrading a project written in C++ 17 to C++ 14"*. – rawrex May 19 '22 at 07:36
  • 1
    `const auto list = {std::make_unique< int >( VALUE ), std::make_unique< int >( 0 ), std::make_unique< int >( 0 )}` won't be possible pre-C++17 and "guaranteed copy-elision". `const std::initializer_list> list{std::make_unique< int >( 42 ), std::make_unique< int >( 0 ), std::make_unique< int >( 0 )};` is possible though [Demo](https://godbolt.org/z/1Wz9jsKj7) (old gcc-5.4 tested). – Jarod42 May 19 '22 at 11:11
  • @Jarod42, please feel free to answer to my question here: https://stackoverflow.com/q/72302914/4688321 – kiner_shah May 19 '22 at 11:28

2 Answers2

1

How about a solution exploiting tag dispatch?

The idea is to move the code from your branches to three auxiliary functions. These functions are overloaded on the last parameter, whose only purpose is to allow you calling the right one later on:

template <typename T>
constexpr const auto& deref(const T& value);

template <typename T>
constexpr const auto& deref(const T& value, std::integral_constant<int, 0>) {
    return deref(*value);
}

template <typename T>
constexpr const auto& deref(const T& value, std::integral_constant<int, 1>) {
    return deref(value.get());
}

template <typename T>
constexpr const auto& deref(const T& value, std::integral_constant<int, 2>) {
    return value;
}

template <typename T>
constexpr const auto& deref(const T& value) {
    using dispatch_t = std::integral_constant<
        int, is_detected_v<has_star_operator, T>
                 ? 0
                 : (is_detected_v<has_get_method, T> ? 1 : 2)>;
    return deref(value, dispatch_t{});
}

With the above implementation, the following compiles:

int main() {
    int VALUE = 42;
    boost::optional<int> optional_value = boost::make_optional(VALUE);
    int a = 42;
    int* b = &a;
    const int array[4] = {VALUE, 0, 0, 0};
    const auto list = {std::make_unique<int>(VALUE),
                       std::make_unique<int>(0), std::make_unique<int>(0)};
    const auto iterator = list.begin();
    std::unique_ptr<int> u = std::make_unique<int>(VALUE);
    std::cout << deref(a) << std::endl;
    std::cout << deref(optional_value) << std::endl;
    std::cout << deref(b) << std::endl;
    std::cout << deref(array) << std::endl;
    std::cout << deref(iterator) << std::endl;
    std::cout << deref(u) << std::endl;
}

and outputs:

42
42
42
42
42
42

Also note that, until C++14, when declaring a template parameter that's a template itself, the syntax is

template <template <typename...> class Operation, typename... Args>
//                               ^ class: you can use typename since C++17
constexpr bool is_detected_v = boost::is_detected<Operation, Args...>::value;
paolo
  • 2,345
  • 1
  • 3
  • 17
  • Thanks for the solution :-) It works, but fails for iterator (probably due to some compiler bug). As mentioned in the question, I am using GCC 5.4 and I get this error when I try to add unique_ptrs to a list: `error: use of deleted function 'constexpr std::__1::unique_ptr >::unique_ptr(const std::__1::unique_ptr >&)'`. Would you be able to provide a way to get this working? https://godbolt.org/z/PM776xGP4 – kiner_shah May 19 '22 at 09:16
  • You should ask a new question about GCC 5.4 failing to initialize a `std::initializer_list`. Also, you hadn't mentioned your compiler nor this error at the time I posted my answer. – paolo May 19 '22 at 09:49
  • Yeah, I think I edited the question after you posted the answer :-P And also, I will create a new question for the list issue. Thanks again for your solution, it works :-D – kiner_shah May 19 '22 at 10:15
  • Would you have any suggestions for the similar problem but with more conditions? – kiner_shah May 19 '22 at 11:30
0

Adding to "tag dispatch" solution provided by @paolo.

I wanted a generic solution which I could use for multiple conditions, so I tried to implement it using templates. It seems to work for most of the cases.

template<int N, class T, template<typename...> class... V>
struct dispatch_constant;

template<int N, class T>
struct dispatch_constant<N, T>
{
    static constexpr int value = N;
};

template<int N, typename T, template<typename...> class U, template<typename...> class... V>
struct dispatch_constant<N, T, U, V...>
{
    static constexpr int value = is_detected_v<U, T> ? N - sizeof...(V) - 1 : dispatch_constant<N, T, V...>::value;
};

Here is the full code:

#include <iostream>
#include <boost/type_traits/is_detected.hpp>
#include <type_traits>
#include <boost/optional/optional.hpp>
#include <memory>
#include <typeinfo>
#include <initializer_list>

template < template < typename... > class Operation, typename... Args >
constexpr bool is_detected_v = boost::is_detected< Operation, Args... >::value;

template < typename T >
using has_star_operator = decltype( *std::declval< T >( ) );

template < typename T >
using has_get_method = decltype( std::declval< T >( ).get( ) );

template <typename T>
constexpr const auto& deref(const T& value);

template <typename T>
constexpr const auto& deref(const T& value, std::integral_constant<int, 0>) {
    std::cout << "STAR " << typeid(T).name() << std::endl;
    return deref(*value);
}

template <typename T>
constexpr const auto& deref(const T& value, std::integral_constant<int, 1>) {
    std::cout << "GET " << typeid(T).name() << std::endl;
    return deref(value.get());
}

template <typename T>
constexpr const auto& deref(const T& value, std::integral_constant<int, 2>) {
    std::cout << "NONE " << typeid(T).name() << std::endl;
    return value;
}

template<int N, class T, template<typename...> class... V>
struct dispatch_constant;

template<int N, class T>
struct dispatch_constant<N, T>
{
    static constexpr int value = N;
};

template<int N, typename T, template<typename...> class U, template<typename...> class... V>
struct dispatch_constant<N, T, U, V...>
{
    static constexpr int value = is_detected_v<U, T> ? N - sizeof...(V) - 1 : dispatch_constant<N, T, V...>::value;
};

template <typename T>
constexpr const auto& deref(const T& value) {
    // using dispatch_t = std::integral_constant<
    //     int, is_detected_v<has_star_operator, T>
    //              ? 0
    //              : (is_detected_v<has_get_method, T> ? 1 : 2)>;
    // std::cout << typeid(T).name() << " " << typeid(dispatch_t).name() << std::endl;
    
    using dispatch_t = std::integral_constant< int, dispatch_constant<2, T, has_star_operator, has_get_method>::value >;
    return deref(value, dispatch_t{});
}

int main() {
    int VALUE = 42;
    boost::optional<int> optional_value = boost::make_optional(VALUE);
    int a = 42;
    int* b = &a;
    const int array[4] = {VALUE, 0, 0, 0};
    std::initializer_list< std::unique_ptr<int> > list = {std::make_unique<int>(VALUE),
                       std::make_unique<int>(0), std::make_unique<int>(0)};
    const auto iterator = list.begin();
    std::unique_ptr<int> u = std::make_unique<int>(VALUE);

    std::cout << deref(a) << std::endl;
    std::cout << deref(optional_value) << std::endl;
    std::cout << deref(b) << std::endl;
    std::cout << deref(array) << std::endl;
    std::cout << deref(iterator) << std::endl;
    std::cout << deref(u) << std::endl;
}
kiner_shah
  • 3,939
  • 7
  • 23
  • 37