将基于 constexpr 的 C++17 模板化代码转换为 C++14

Convert if constexpr based C++17 templatized code to C++14

我正在将一个用 C++ 17 编写的项目降级到 C++ 14。降级时,我遇到了一段涉及 if constexpr 的代码,我希望将其转换为 C++ 14(据我所知, if constexpr 是 C++ 17 的特性)。

Boost 的 is_detected 用于检查给定类型是否具有星号运算符或 get 方法。

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

有一个函数调用 deref 用于取消引用指针、数组、迭代器、智能指针等类型

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

我试图通过使用 std::enable_if 来形成一个没有 if constexpr 的解决方案,如下所示:

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

但是,对于迭代器和智能指针等必须进行多次取消引用的情况,上述方法失败了。例如,对于 std::unique_ptr,将首先调用 p.get() (auto q = p.get()),然后调用星号运算符 (*q)。

我是模板初学者,需要一些帮助。请告诉我如何解决这个问题。

我正在使用 GCC 5.4 编译。

利用标签分派的解决方案怎么样?

想法是将代码从您的分支移动到三个辅助函数。 这些函数在最后一个参数上重载,其唯一目的是 允许您稍后调用正确的:

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

通过上面的实现,编译如下:

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

并输出:

42
42
42
42
42
42

另请注意,在 C++14 之前,当声明本身就是模板的模板参数时,语法是

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 提供的“标签分发”解决方案。

我想要一个可以用于多种情况的通用解决方案,所以我尝试使用模板来实现它。它似乎适用于大多数情况。

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

完整代码如下:

#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;
}