51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

在C++中,可以使用可变模板参数来检索类型的内部类型。

英文:

Is there a way to retrieve the inner types of a type using variadic templates in C++?

问题 {#heading}

假设我有一个使用可变模板的类型:

template <typename... Args>
struct Outer
{
  // using Inner = something that captures ...Args ???;
}

我如何定义Inner,以便我以后可以在其他一些模板代码中使用它,例如:

// ... 一些接受其他模板参数的现有函数
template <typename... Args2>
foo(Args2 ...)
{
 // ...
};
// ... 在某处定义一些特定的Outers,例如但不限于
using Specific1 = Outer<int, double, bool>;
using Specific2 = Outer<std::string>;
// ...
foo(Specific1::Inner... args)
// ...
foo(Specific2::Inner... args)

我主要关注的是C++17,但也愿意学习在任何其他版本的C++中如何实现。理想情况下,我希望在不使用std::tuple的情况下实现这一点。

一个最小可复现的示例:

template <typename... Args>
struct Outer
{
	using Cb = std::function<void(Args...)>;

	//using Inner = Args; // 这个会报错:"parameter pack must be expanded in this context"
	//using Inner = Args...; // 这个会报错:"parameter pack cannot be expanded in this context"

	template<typename CbIn>
	void store(CbIn&& cb)
	{
		mCb = std::forward<CbIn>(cb);
	}

	void call(Args... args) noexcept
	{
		mCb(args...);
	}

	// cb here accepts Args... and returns OtherOuter* (specialized with different Args)
	template<typename OtherOuter, typename CbIn>
	void foo(CbIn&& cb)
	{
		store([cb{ std::forward<CbIn>(cb)}](Args... args)
		{
			OtherOuter * other = cb(std::forward<Args>(args)...);
			other->store([](/*OtherOuter::Inner*/ ... otherArgs) // 如果不是OtherOuter::Inner,这里应该放什么?
			{
				// 对otherArgs做一些操作
				([&]
				{
					std::cout << "second " << otherArgs << std::endl;
				} (), ...);
			});
			std::cout << "first " << other->mFlag << std::endl;
		});
	}

	Cb mCb;
	bool mFlag = false;
};

使用这个示例:

using OuterIntBool = Outer<int, bool>;
using OuterString = Outer<std::string>;
// 正常工作
{
	OuterIntBool outerIntBool;
	outerIntBool.store([](int i, bool b)
	{
		bool isValid = i > 0 && b;
		assert(isValid);
	});
	outerIntBool.call(1, true);
}
// 不工作
{
	OuterIntBool outerIntBool;
	OuterString otherString;
	outerIntBool.foo<OuterString>([&otherString](int/* i*/, bool b)
	{
		otherString.mFlag = b;
		return &otherString;
	});
	outerIntBool.call(1, true);
	otherString.call("bar");
}

英文:

Assume I have a type using variadic templates:

template &lt;typename... Args&gt;
struct Outer
{
  // using Inner = something that captures ...Args ???;
}

How could I define Inner such that I could later use it elsewhere in some templated code like:

// ... some existing function accepting potentially other template arguments
template &lt;typename... Args2&gt;
foo(Args2 ...)
{
 // ...
};
// ... somewhere I define some specialized Outers, as examples but not limited to
using Specific1 = Outer&lt;int, double, bool&gt;;
using Specific2 = Outer&lt;std::string&gt;;
// ...
foo(Specific1::Inner... args)
// ...
foo(Specific2::Inner... args)

I am mainly interested on C++17, but open to learn however it could be done in any other version of C++. Ideally I would like to achieve this without joggling around with std::tuple.

A minimal reproducible example:

template &lt;typename... Args&gt;
struct Outer
{
	using Cb = std::function&lt;void(Args...)&gt;;

    //using Inner = Args; // this one complains &amp;quot;parameter pack must be expanded in this context&amp;quot;
    //using Inner = Args...; // this one complains &amp;quot;parameter pack cannot be expanded in this context&amp;quot;

    template&amp;lt;typename CbIn&amp;gt;
    void store(CbIn&amp;amp;&amp;amp; cb)
    {
    	mCb = std::forward&amp;lt;CbIn&amp;gt;(cb);
    }

    void call(Args... args) noexcept
    {
    	mCb(args...);
    }

    // cb here accepts Args... and returns OtherOuter* (specialized with different Args)
    template&amp;lt;typename OtherOuter, typename CbIn&amp;gt;
    void foo(CbIn&amp;amp;&amp;amp; cb)
    {
    	store([cb{ std::forward&amp;lt;CbIn&amp;gt;(cb)}](Args... args)
    	{
    		OtherOuter * other = cb(std::forward&amp;lt;Args&amp;gt;(args)...);
    		other-&amp;gt;store([](/*OtherOuter::Inner*/ ... otherArgs) // what to put here if not OtherOuter::Inner ???
    		{
    			// do something with otherArgs
    			([&amp;amp;]
    			{
    				std::cout &amp;lt;&amp;lt; &amp;quot;second &amp;quot; &amp;lt;&amp;lt; otherArgs &amp;lt;&amp;lt; std::endl;
    			} (), ...);
    		});
    		std::cout &amp;lt;&amp;lt; &amp;quot;first &amp;quot; &amp;lt;&amp;lt; other-&amp;gt;mFlag &amp;lt;&amp;lt; std::endl;
    	});
    }

    Cb mCb;
    bool mFlag = false;



`};
`

And using the example:

using OuterIntBool = Outer&lt;int, bool&gt;;
using OuterString = Outer&lt;std::string&gt;;
// works
{
	OuterIntBool outerIntBool;
	outerIntBool.store([](int i, bool b)
	{
		bool isValid = i &gt; 0 &amp;&amp; b;
		assert(isValid);
	});
	outerIntBool.call(1, true);
}
// does not work
{
	OuterIntBool outerIntBool;
	OuterString otherString;
	outerIntBool.foo&lt;OuterString&gt;([&amp;otherString](int/* i*/, bool b)
	{
		otherString.mFlag = b;
		return &amp;otherString;
	});
	outerIntBool.call(1, true);
	otherString.call(&quot;bar&quot;);
}

答案1 {#1}

得分: 3

如何"保存"可变模板参数?

直接的方式,就像我们对待常规模板参数一样,可行:

template <typename T>
struct s1
{
    using type = T; // OK
};

template <typename... Ts>
struct s2
{
    using types = Ts...; // KO
};

但是,有几种方法可以"保存"可变参数:

  • 使用某种可变类型来保存它,例如std::tuple或您自己的type_list

    template <typename... Ts> struct type_list {};
    
    template <typename... Args>
    struct Outer
    {
        using type1s = std::tuple<Args...>;
        using type2s = type_list<Args...>;
    };
    
  • 一种解构的方式:

    template <typename... Args>
    struct Outer
    {
        constexpr type_size = sizeof...(Args);
        template <std::size_t I>
        using type = std::tuple_element_t<I, std::tuple<Args...>>;
    };
    
  • 使用外部类型特性(与上述任何结果都可以)。例如解构的方式示例:

    template <typename T>
    constexpr std::size_t outer_size;
    
    template <typename... Ts>
    constexpr std::size_t outer_size<Outer<Ts...>> = sizeof...(Ts);
    
    template <std::size_t I, typename T>
    struct arg_element;
    
    template <std::size_t I, typename... Ts>
    struct arg_element<I, Outer<Ts...>> {
        using type = std::tuple_element_t<I, std::tuple<Ts...>>;
    };
    

如何使用保存的参数?

std::index_sequence可能会有所帮助:

template <typename Outer>
void get_printer(const Outer&)
{
    []<std::size_t... Is>(std::index_sequence<Is...>){
        // typename Outer::template type<Is>... is not equivalent to the Ts...
        // std::tuple_element_t<Is, typename Outer::types>... for tuple case

        return [](const typename Outer::template type<Is>&... args){
            (std::cout << args), ...);
        };
    }(std::make_index_sequence<Outer::size_type>()); // tuple_size<Outer::types>;
}

英文:

> How to "save" variadic template parameter?

The direct way, as we do with regular template argument is not possible:

template &lt;typename T&gt;
struct s1
{
    using type = T; // OK
};
`template &lt;typename... Ts&gt;
struct s2
{
using types = Ts...; // KO
};
`

but, they are several ways to "save" variadic arguments:

  • Use some variadic type to keep it std::tuple or your own type_list

    template &lt;typename... Ts&gt; struct type_list {};
    `template &lt;typename... Args&gt;
    struct Outer
    {
    using type1s = std::tuple&lt;Args...&gt;;
    using type2s = type_list&lt;Args...&gt;;
    };
    `
    
  • A destructured way:

    template &lt;typename... Args&gt;
    struct Outer
    {
        constexpr type_size = sizeof...(Args);
        template &lt;std::size_t I&gt;
        using type = std::tuple_element_t&lt;I, std::tuple&lt;Args...&gt;&gt;;
    };
    
  • an external type_trait (with any of the above result). so destructured way example:

    template &lt;typename T&gt;
    constexpr std::size_t outer_size;
    
    template \&lt;typename... Ts\&gt;
    constexpr std::size_t outer_size\&lt;Outer\&lt;Ts...\&gt;\&gt; = sizeof...(Ts);
    
    
    template \&lt;std::size_t I, typename T\&gt;
    struct arg_element;
    
    `template &lt;std::size_t I, typename... Ts&gt;
    struct arg_element&lt;I, Outer&lt;Ts...&gt;&gt; {
    using type = std::tuple_element_t&lt;Is, std::tuple&lt;Ts...&gt;&gt;;
    };
    `
    

> How to use saved parameters?

std::index_sequence might help:

template &lt;typename Outer&gt;
void get_printer(const Outer&amp;)
{
    []&lt;std::size_t... Is&gt;(std::index_sequence&lt;Is...&gt;){
        // typename Outer::template type&lt;Is&gt;... is not equivalent to the Ts...
        // std::tuple_element_t&lt;Is, typename Outer::types&gt;... for tuple case

        return [](const typename Outer::template type&amp;lt;Is&amp;gt;&amp;amp;... args){
            (std::cout &amp;lt;&amp;lt; args), ...);
        };
    }(std::make_index_sequence&amp;lt;Outer::size_type&amp;gt;()); // tuple_size&amp;lt;Outer::types&amp;gt;



`}
`

答案2 {#2}

得分: 1

你尝试过 other->store([](auto&& ... otherArgs) 吗? 英文:

Did you try other-&gt;store([](auto&amp;&amp; ... otherArgs)?


赞(0)
未经允许不得转载:工具盒子 » 在C++中,可以使用可变模板参数来检索类型的内部类型。