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

huangapple go评论201阅读模式
英文:

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

问题

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

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 &quot;parameter pack must be expanded in this context&quot;
	//using Inner = Args...; // this one complains &quot;parameter pack cannot be expanded in this context&quot;

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

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

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

得分: 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&lt;Is&gt;&amp;... args){
            (std::cout &lt;&lt; args), ...);
        };
    }(std::make_index_sequence&lt;Outer::size_type&gt;()); // tuple_size&lt;Outer::types&gt;


}

答案2

得分: 1

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

英文:

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

huangapple
  • 本文由 发表于 2023年8月11日 15:16:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76881423.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定