std::apply 中的 std::make_shared

3
考虑以下代码示例。
它创建了一个名为A的类(带有模板参数T和Args...),并将其构造函数参数保存到元组args_中。稍后,它使用args_在get方法内部创建T的实例。
所有东西都很好,但是正如你所看到的,我在std::apply中使用了my_own_make_shared函数,原因是如果我用std::make_shared替换它,则无法编译此代码。有人有任何想法是什么问题吗?
#include <iostream>
#include <charconv>
#include <array>
#include <cstring>
#include <iostream>
#include <tuple>
#include <utility>
#include <memory>
#include <algorithm>

struct Base
{
    virtual void foo() = 0;
};

struct Test : Base
{
    Test(int, int) {}
    void foo() override {std::cout << "Test\n";}
};

struct Test2 : Base
{
    Test2(int, int, const std::string&) {}
    void foo() override {std::cout << "Test2\n";}
};

template<typename T, typename... Args>
std::shared_ptr<T> my_own_make_shared(Args... args)
{
    return std::make_shared<T>(args...);
}

template <typename T, typename... Args>
struct A
{
    A(Args... args) : args_(std::make_tuple(args...)) {}

    std::shared_ptr<Base> get()
    {
        return std::apply(my_own_make_shared<T, Args...>, args_);
    }

    std::tuple<Args...> args_;
};

template <typename T, typename... Args>
auto make_A(Args... args)
{
    return A<T, Args...>(args...);
}

int main(int argc, char** argv)
{
    make_A<Test>(1, 2).get()->foo();
    make_A<Test2>(1, 2, "").get()->foo();
}

编译器错误 (GCC-9)。假设我们用 std::make_shared 替换了 my_own_make_shared
    In file included from test.cpp:6:
/usr/include/c++/9/tuple: In instantiation of ‘constexpr decltype(auto) std::__apply_impl(_Fn&&, _Tuple&&, std::index_sequence<_Idx ...>) [with _Fn = std::shared_ptr<Test> (&)(int&&, int&&); _Tuple = std::tuple<int, int>&; long unsigned int ..._Idx = {0, 1}; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1>]’:
/usr/include/c++/9/tuple:1694:31:   required from ‘constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = std::shared_ptr<Test> (&)(int&&, int&&); _Tuple = std::tuple<int, int>&]’
test.cpp:41:26:   required from ‘std::shared_ptr<Base> A<T, Args>::get() [with T = Test; Args = {int, int}]’
test.cpp:55:28:   required from here
/usr/include/c++/9/tuple:1684:27: error: no matching function for call to ‘__invoke(std::shared_ptr<Test> (&)(int&&, int&&), std::__tuple_element_t<0, std::tuple<int, int> >&, std::__tuple_element_t<1, std::tuple<int, int> >&)’
 1684 |       return std::__invoke(std::forward<_Fn>(__f),
      |              ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
 1685 |       std::get<_Idx>(std::forward<_Tuple>(__t))...);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/9/tuple:41,
                 from test.cpp:6:
/usr/include/c++/9/bits/invoke.h:89:5: note: candidate: ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...)’
   89 |     __invoke(_Callable&& __fn, _Args&&... __args)
      |     ^~~~~~~~
/usr/include/c++/9/bits/invoke.h:89:5: note:   template argument deduction/substitution failed:
/usr/include/c++/9/bits/invoke.h: In substitution of ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...) [with _Callable = std::shared_ptr<Test> (&)(int&&, int&&); _Args = {int&, int&}]’:
/usr/include/c++/9/tuple:1684:27:   required from ‘constexpr decltype(auto) std::__apply_impl(_Fn&&, _Tuple&&, std::index_sequence<_Idx ...>) [with _Fn = std::shared_ptr<Test> (&)(int&&, int&&); _Tuple = std::tuple<int, int>&; long unsigned int ..._Idx = {0, 1}; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1>]’
/usr/include/c++/9/tuple:1694:31:   required from ‘constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = std::shared_ptr<Test> (&)(int&&, int&&); _Tuple = std::tuple<int, int>&]’
test.cpp:41:26:   required from ‘std::shared_ptr<Base> A<T, Args>::get() [with T = Test; Args = {int, int}]’
test.cpp:55:28:   required from here
/usr/include/c++/9/bits/invoke.h:89:5: error: no type named ‘type’ in ‘struct std::__invoke_result<std::shared_ptr<Test> (&)(int&&, int&&), int&, int&>’
test.cpp: In instantiation of ‘std::shared_ptr<Base> A<T, Args>::get() [with T = Test; Args = {int, int}]’:
test.cpp:55:28:   required from here
test.cpp:41:26: error: could not convert ‘std::apply<std::shared_ptr<Test> (&)(int&&, int&&), std::tuple<int, int>&>(std::make_shared<Test, int, int>, ((A<Test, int, int>*)this)->A<Test, int, int>::args_)’ from ‘void’ to ‘std::shared_ptr<Base>’
   41 |         return std::apply(std::make_shared<T, Args...>, args_);
      |                ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                          |
      |                          void
In file included from test.cpp:6:
/usr/include/c++/9/tuple: In instantiation of ‘constexpr decltype(auto) std::__apply_impl(_Fn&&, _Tuple&&, std::index_sequence<_Idx ...>) [with _Fn = std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&); _Tuple = std::tuple<int, int, const char*>&; long unsigned int ..._Idx = {0, 1, 2}; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1, 2>]’:
/usr/include/c++/9/tuple:1694:31:   required from ‘constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&); _Tuple = std::tuple<int, int, const char*>&]’
test.cpp:41:26:   required from ‘std::shared_ptr<Base> A<T, Args>::get() [with T = Test2; Args = {int, int, const char*}]’
test.cpp:56:33:   required from here
/usr/include/c++/9/tuple:1684:27: error: no matching function for call to ‘__invoke(std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&), std::__tuple_element_t<0, std::tuple<int, int, const char*> >&, std::__tuple_element_t<1, std::tuple<int, int, const char*> >&, const char*&)’
 1684 |       return std::__invoke(std::forward<_Fn>(__f),
      |              ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
 1685 |       std::get<_Idx>(std::forward<_Tuple>(__t))...);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/9/tuple:41,
                 from test.cpp:6:
/usr/include/c++/9/bits/invoke.h:89:5: note: candidate: ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...)’
   89 |     __invoke(_Callable&& __fn, _Args&&... __args)
      |     ^~~~~~~~
/usr/include/c++/9/bits/invoke.h:89:5: note:   template argument deduction/substitution failed:
/usr/include/c++/9/bits/invoke.h: In substitution of ‘template<class _Callable, class ... _Args> constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...) [with _Callable = std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&); _Args = {int&, int&, const char*&}]’:
/usr/include/c++/9/tuple:1684:27:   required from ‘constexpr decltype(auto) std::__apply_impl(_Fn&&, _Tuple&&, std::index_sequence<_Idx ...>) [with _Fn = std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&); _Tuple = std::tuple<int, int, const char*>&; long unsigned int ..._Idx = {0, 1, 2}; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1, 2>]’
/usr/include/c++/9/tuple:1694:31:   required from ‘constexpr decltype(auto) std::apply(_Fn&&, _Tuple&&) [with _Fn = std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&); _Tuple = std::tuple<int, int, const char*>&]’
test.cpp:41:26:   required from ‘std::shared_ptr<Base> A<T, Args>::get() [with T = Test2; Args = {int, int, const char*}]’
test.cpp:56:33:   required from here
/usr/include/c++/9/bits/invoke.h:89:5: error: no type named ‘type’ in ‘struct std::__invoke_result<std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&), int&, int&, const char*&>’
test.cpp: In instantiation of ‘std::shared_ptr<Base> A<T, Args>::get() [with T = Test2; Args = {int, int, const char*}]’:
test.cpp:56:33:   required from here
test.cpp:41:26: error: could not convert ‘std::apply<std::shared_ptr<Test2> (&)(int&&, int&&, const char*&&), std::tuple<int, int, const char*>&>(std::make_shared<Test2, int, int, const char*>, ((A<Test2, int, int, const char*>*)this)->A<Test2, int, int, const char*>::args_)’ from ‘void’ to ‘std::shared_ptr<Base>’
   41 |         return std::apply(std::make_shared<T, Args...>, args_);
      |                ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                          |
      |                          void

看起来你没有正确地转发参数。 - user7860670
是的,看起来我们只能移动参数。我想这有点奇怪。 - Dmitry
args_ 是 Lvalue。因此当调用 apply 时,除第一个参数(要调用的函数对象)外的所有参数都作为 Lvalue 转发,这与接受所有参数为 Rvalue 的 make_shared 不匹配。以下代码可行:return std::apply(std::make_shared<T,Args&...>, args_);示例 - rafix07
1个回答

2
以下是从参考文献中获取的std::apply实现。
namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
    // This implementation is valid since C++20 (via P1065R2)
    // In C++17, a constexpr counterpart of std::invoke is actually needed here
    return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
}  // namespace detail

正如我们所看到的,函子及其参数作为元组的元素被转发。

在您的情况下,args_ 是 Lvaue。因此,通过 std::get<I>(std::forward<Tuple>(t))... 您可以获取特定元组元素的左值引用。

对于

make_A<Test>(1, 2).get()->foo();

参数包Args...被推导为:{int,int},并且以下

return std::apply(std::make_shared<T, Args...>, args_);

无法编译,因为R值引用不能绑定到L值。 R值引用出现在make_shared参数列表中。您可以使用此重载

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

其中Args...{int,int},所以参数列表为int&&, int&&

如果您想将元组的元素作为apply的Lvalue参数使用,只需将Args强制转换为Lvalues:

return std::apply(std::make_shared<T, std::add_lvalue_reference_t<Args>...>, args_);

Demo


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接