【问题标题】:C++: static assert that functor's argument is const referenceC++:静态断言仿函数的参数是常量引用
【发布时间】:2021-04-29 11:53:47
【问题描述】:

我正在C++17 中编写一个模板函数,它接受一个函子F 作为参数,我想限制传入的函子只有一个常量引用参数,其中T 可以是任何类型。

例如:

template <class T> struct my_struct{
    std::vector<T> collection;
    template <class F> std::vector<T> func(F f){
        static_assert(
               // CONDITION HERE!,
               "f's argument is not const reference"
            );

        std::vector<T> ret;

        std::copy_if(
                std::make_move_iterator(this->collection.begin()),
                std::make_move_iterator(this->collection.end()),
                std::inserter(ret, ret.end()),
                f
            );

        return ret;
    }
};

显然,如果f[](auto v){return true;},则从func 返回的结果向量将包含空元素(因为这些元素在添加到结果容器之前已被移动)。所以,我需要将可能的输入函子限制为[](const auto&amp; v){}

我尝试过这样的事情:

static_assert(
        std::is_invocable_v<F, const T&>,
        "f's argument is not const reference"
    );

但是func([](auto v){}) 不会触发断言,因为在我的情况下T 是可复制的。 func([](auto&amp; v){}) 也通过了测试,因为auto 可以是const T

但我需要将可能的 lambda 限制为 func([](const auto&amp; v){})

【问题讨论】:

  • "auto 本身可以是const T&amp;" - 不能。它是一种价值类型。它通过了测试,因为类型是可复制的。你想达到什么目的?为什么“仅通过 const-ref”似乎是解决方案?
  • @StoryTeller-UnslanderMonica 对,因为它是可复制的。正如我所说,我试图阻止 F 的参数被移动或修改。
  • 听起来你只需要对传递给函数的东西进行右值到左值的转换。使用const auto&amp;&amp; lvalue_temp = expression_that_generates_temporary; 然后将lvalue_temp 传递给f 将确保它不能被移动或修改。
  • 那为什么不直接通过std::as_const 传递呢?它会触发修改(不考虑const_cast,但我们应该假设函子是理智的)。如果不广泛,您也可以制作本地副本。让函子拥有这些。
  • 我已经在问题中添加了更多信息,我拥有或多或少的确切代码。可以看到,我正在使用移动迭代器和std::copy_if。这可能会提供有关我需要的更多信息。

标签: c++ c++17 metaprogramming template-meta-programming


【解决方案1】:

您可以编写特征(有其局限性),例如:

template <typename Sig> struct callable_traits;

template <typename Ret, typename ...Args>
struct callable_traits<Ret(*)(Args...)>
{
    using args = std::tuple<Args...>;
};
// add specialization for C-ellipsis too

template <typename Ret, class C, typename ...Args>
struct callable_traits<Ret(C::*)(Args...) const>
{
    using args = std::tuple<Args...>;
};
// add specialization for variant with C-ellipsis, cv-qualifier, ref-qualifier

template <class C>
struct callable_traits<C> : callable_traits<&C::operator()>{};

特征的限制:不处理模板化 operator()(对于通用 lambda),重载 operator()

然后

template <class T> struct my_struct{
    template <class F> void func(F f){
        static_assert(
               std::is_same_v<std::tuple<const T&>, typename callable_traits<F>::args>,
               "f's argument is not const reference"
            );

        // here goes some code which can possibly call f with rvalue
        // reference argument, so I want to avoid situation when the
        // argument object is moved or modified. I don't have control
        // over this code because it an STL algorithm.
    }
};

【讨论】:

  • 感谢您的建议,这看起来应该可行。但是,不幸的是,有一个阻止程序。您提到这不适用于泛型 lambda,但我希望能够使用真正的泛型 lambda,这至关重要,因为编写确切的类型可能太难了,而且可能很长。
【解决方案2】:

我可能误解了您要执行的操作,但是,当我阅读时,您想接受一个可调用对象,然后将一些参数传递给它,并保证该参数不能更改(您不希望有人接受参数作为非常量左值引用或右值引用。如果是这样,那么std::is_invocable 应该足够了:

#include <type_traits> // for std::is_invocable
#include <functional> // for std::invoke

template <class parameter_t> struct my_struct {
    template <typename callable_t>
    void function(callable_t const &callable) requires (
        std::is_invocable<callable_t, parameter_t const &>::value
    ) {
        // . . .
        std::invoke(callable, parameter_t{});
        // . . .
    }
};

然后:

int main() {
    my_struct<int>{}.function([](int const&){}); // Fine
    my_struct<int>{}.function([](int){}); // Fine, a copy of the parameter is made when the lambda is invoked.
    my_struct<int>{}.function([](int &){}); // error: no matching member function for call to 'function'
    my_struct<int>{}.function([](int &&){}); // error: no matching member function for call to 'function'
}

(你可以玩弄它here

一个可能的问题是这种方法确实允许进行复制,但如果主要目标是保护您持有的变量不被更改,这应该足够了。

P。 S. 我知道我使用 c++20 requires 子句作为未来验证答案的一种方式,但将其转换为 if constexprstatic_assert 或您喜欢的任何其他方式应该是微不足道的。

【讨论】:

  • 不幸的是,这对我不起作用。请参阅原始更新说明。
【解决方案3】:

我终于设法通过以下方式实现了它:


template <class T> struct my_struct{
    std::vector<T> collection;

    struct noncopyable_value_type : T{
        noncopyable_value_type(const noncopyable_value_type&) = delete;
        noncopyable_value_type& operator=(const noncopyable_value_type&) = delete;
    };

    template <class F> std::vector<T> func(F f){
        static_assert(
               std::is_invocable_v<F, noncopyable_value_type>,
               "f's argument must be const reference"
            );

        std::vector<T> ret;

        std::copy_if(
                std::make_move_iterator(this->collection.begin()),
                std::make_move_iterator(this->collection.end()),
                std::inserter(ret, ret.end()),
                f
            );

        return ret;
    }
};

但是,这里的问题是它只适用于通用 lambda。

【讨论】:

    猜你喜欢
    • 2021-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    相关资源
    最近更新 更多