std::invocable 的正确参数化

问题描述 投票:0回答:1

以下代码在注释掉概念时打印所有内容:

#include <cstdint>
#include <concepts>
#include <print>
#include <type_traits>

template<typename T>
struct ValueHolder {
    using ValueType = T;

    T value;
};

template<
    typename Value,
    typename T = std::remove_reference_t<Value>::ValueType,
    // typename Arg,
    typename Callable
>
// requires
    // std::invocable<Callable, T>
    // std::assignable_from<Arg, T>
    // std::convertible_to<T, Arg>
    //     && std::invocable<Callable, Arg>
void callOnValue(Value&& val, Callable&& callable)
{
    std::forward<Callable>(callable)(std::forward<Value>(val).value);
}

void printI32(std::int32_t) { std::println("i32"); }
void printI32Ref(std::int32_t&) { std::println("i32&"); }
void printI32RefRef(std::int32_t&&) { std::println("i32&&"); }
void printConstI32Ref(const std::int32_t &) { std::println("const i32&"); }

void printI16(std::int16_t) { std::println("i16"); }
void printI64(std::int64_t) { std::println("i64"); }

int main()
{
    ValueHolder i{static_cast<std::int32_t>(42)};
    callOnValue(i, printI32);
    callOnValue(i, printI32Ref);

    callOnValue(ValueHolder{static_cast<std::int32_t>(42)}, printI32RefRef);

    const auto ci{i};
    callOnValue(ci, printI32);
    callOnValue(ci, printConstI32Ref);

    callOnValue(i, printI16);
    callOnValue(i, printI64);
}

取消评论

requires
    std::invocable<Callable, T>

无法拨打电话

printIntRef
。取消注释
Arg
以及
std::assignable_from
std::convertible_to
会导致无法推断出
Arg

  1. callable
    上的什么概念将允许重现所有默认传递(即
    callable
    接受按值传递、通过 const ref、通过左值的 ref 和通过右值的 ref-ref 传递)?
  2. 如果我想要所有传递但又想防止缩小转换(
    printI16
    不能编译,
    printI64
    可以)怎么办?
  3. 如果
    T
    中的
    ValueHolder
    Derived&
    Derived*
    ,如何约束
    callable
    为逆变,即接受
    Base&
    Base*
  4. 如何防止切片(按值从
    Derived
    Base
    )?
c++ templates c++-concepts
1个回答
0
投票

问题在于你对自己真正想做的事情感到困惑。

首先,

callOnValue
应该是这样的

void callOnValue(Value&& val, Callable&& callable)
{
    std::forward<Callable>(callable)(std::forward_like<Value>(val.value));
}

否则,您会在正确转发会员值时遇到问题。

std::forward_like
来自 C++23,因此您可以使用替代实现:

template<class T, class U>
constexpr auto&& forward_like(U&& x) noexcept
{
    constexpr bool is_adding_const = std::is_const_v<std::remove_reference_t<T>>;
    if constexpr (std::is_lvalue_reference_v<T&&>)
    {
        if constexpr (is_adding_const)
            return std::as_const(x);
        else
            return static_cast<U&>(x);
    }
    else
    {
        if constexpr (is_adding_const)
            return std::move(std::as_const(x));
        else
            return std::move(x);
    }
}

第二个问题是模板中的

Value
可能是常量,所以要引用
ValueType
Value
最好这样写。

typename T = std::remove_cvref_t<Value>::ValueType

第三个问题是如何测试是否可以正确调用。怎么做呢?您需要将

Value
的特征复制到
T
并测试结果的可调用性。您可以通过自定义类型特征来做到这一点:

template<class T, class U>
struct copy_traits
{
    using type = decltype(forward_like<T>(std::declval<U>()));
};

template<class T, class U>
using copy_traits_t = typename copy_traits<T,U>::type; 

其中

forward_like
如上所述,并测试其可调用性
by std::invocable<Callable, copy_traits_t<Value, T> >

你会得到这样的代码:

#include <cstdint>
#include <concepts>
#include <iostream>
#include <type_traits>
#include <utility>

template<typename T>
struct ValueHolder {
    using ValueType = T;

    T value;
};

template<class T, class U>
constexpr auto&& forward_like(U&& x) noexcept
{
    constexpr bool is_adding_const = std::is_const_v<std::remove_reference_t<T>>;
    if constexpr (std::is_lvalue_reference_v<T&&>)
    {
        if constexpr (is_adding_const)
            return std::as_const(x);
        else
            return static_cast<U&>(x);
    }
    else
    {
        if constexpr (is_adding_const)
            return std::move(std::as_const(x));
        else
            return std::move(x);
    }
}

template<class T, class U>
struct copy_traits
{
    using type = decltype(forward_like<T>(std::declval<U>()));
};

template<class T, class U>
using copy_traits_t = typename copy_traits<T,U>::type;

template<
    typename Value,
    typename Callable,
    typename T = std::remove_cvref_t<Value>::ValueType
>
requires (std::invocable<Callable, copy_traits_t<Value, T> >)
void callOnValue(Value&& val, Callable&& callable)
{
    std::forward<Callable>(callable)(forward_like<Value>(val.value));
}

void printI32(std::int32_t) { std::cout << "i32\n"; }
void printI32Ref(std::int32_t&) { std::cout << "i32&\n"; }
void printI32RefRef(std::int32_t&&) {std::cout << "i32&&\n"; }
void printConstI32Ref(const std::int32_t &) {std::cout << "const i32&\n"; }

void printI16(std::int16_t) { std::cout << "i16\n"; }
void printI64(std::int64_t) { std::cout << "i16\n"; }

int main()
{
    ValueHolder i{static_cast<std::int32_t>(42)};
    callOnValue(i, printI32);
    callOnValue(i, printI32Ref);

    callOnValue(ValueHolder{static_cast<std::int32_t>(42)}, printI32RefRef);
    const auto ci{i};
    callOnValue(ci, printI32);
    callOnValue(ci, printConstI32Ref);

    callOnValue(i, printI16);
    callOnValue(i, printI64);
}

或者,您也可以编写一个自定义概念:

requires (requires (Value&& val, Callable&& callable)
{
      {std::forward<Callable>(callable)(forward_like<Value>(val.value))};
})
© www.soinside.com 2019 - 2024. All rights reserved.