如何命名模板的依赖返回类型

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

How to name dependent return type of template

问题

使用 std::enable_ifstd::conditional,我们可以创建模板函数,其返回类型取决于其模板参数,如std::enable_if示例所示:

template<class T>
typename std::enable_if<std::is_trivially_default_constructible<T>::value>::type 
construct(T*) 
{
    std::cout << "default constructing trivially default constructible T\n";
}

如何在函数体中定义一个可用于此返回类型的类型名称?

不幸的是,正如同一篇文章的注意事项中所警告的,我们不能将条件表达式直接放入命名的模板参数中:

/* 错误的方式 */
 
struct T
{
    enum { int_t, float_t } type;
 
    template<typename Integer,
             typename = std::enable_if_t<std::is_integral<Integer>::value>>
    T(Integer) : type(int_t) {}
 
    template<typename Floating,
             typename = std::enable_if_t<std::is_floating_point<Floating>::value>>
    T(Floating) : type(float_t) {} // 错误:被视为重新定义
};

当然,我们可以将定义复制到函数体的第一行,并使用 using 指令,但那样我们将不得不维护两个定义 - 嵌套的std::conditional_t并不是可读性的巅峰(但比旧式CRTP好多了)。

是否有一种方法可以命名该类型而不重复定义它?

英文:

Using std::enable_if or std::conditional we can create template functions whose return type depends on their template parameters, as per the std::enable_if example:

template&lt;class T&gt;
typename std::enable_if&lt;std::is_trivially_default_constructible&lt;T&gt;::value&gt;::type 
construct(T*) 
{
    std::cout &lt;&lt; &quot;default constructing trivially default constructible T\n&quot;;
}

(shortened for brevity)

How can we define a typename usable in the function body for this return type?

Unfortunately, as cautioned in the notes of the same article, we cannot just put the conditional expression into a named template parameter:

> default template arguments are not accounted for in function template equivalence

/* WRONG */
 
struct T
{
    enum { int_t, float_t } type;
 
    template&lt;typename Integer,
             typename = std::enable_if_t&lt;std::is_integral&lt;Integer&gt;::value&gt;&gt;
    T(Integer) : type(int_t) {}
 
    template&lt;typename Floating,
             typename = std::enable_if_t&lt;std::is_floating_point&lt;Floating&gt;::value&gt;&gt;
    T(Floating) : type(float_t) {} // error: treated as redefinition
};

We could of course copy the definition into a using-directive in the first line of the function body, but then we would have to maintain both definitions - and nested std::conditional_t are not exactly the pinnacle of legibility (just way, way better than old school CRTP).

Is there a way to name the type without duplicating its definition?

答案1

得分: 0

您提供的文本中包含了一些关于C++编程中的SFINAE和模板元编程的内容。以下是翻译好的部分:

"The example you linked and took inspiration from aims at enabling a function using SFINAE (Substitution Failure Is Not An Error) on the return type. std::enable_if, if not instructed otherwise, resolves to a type that, if the substitution succeeded, contains a type field which is an alias for the void type. In essence, the return type of the example function is just void, if the substitution succeeds, otherwise the function gets discarded and compilation proceeds (SFINAE)."

你链接并从中汲取灵感的示例旨在使用SFINAE(Substitution Failure Is Not An Error)来启用函数,其关键在于返回类型。std::enable_if,如果没有特别指定,会解析为一个类型,如果替换成功,它会包含一个type字段,该字段是void类型的别名。从本质上讲,示例函数的返回类型只是void,如果替换成功,否则函数将被丢弃,编译继续进行(SFINAE)。

"Reading your question, I had understood you want to replicate that behavior on your class constructors, that is enable/disable them according to the type of the constructor's argument, in which case the syntax that would make it work could be the following."

阅读您的问题,我理解您希望在您的类构造函数上复制该行为,即根据构造函数参数的类型启用/禁用它们,在这种情况下,使其工作的语法可能如下所示。

std::enable_if_t<std::is_floating_point_v<Floating>>*会在替换成功时扩展为void*,因此模板参数将是指向void的指针,即“非类型”参数,我们将其默认为nullptr。这可以工作,与您之前的示例相比,因为非类型模板参数可以具有任何其他值,而您的尝试中的两个构造函数中的void将是相同的,因此编译器会将其视为重新定义。

"But by reading your comments to your own question, I think I understood what you really want to achieve."

但通过阅读您对自己问题的评论,我认为我理解了您真正想要实现的目标。

以下函数,以您在评论中提到的方式命名,具有依赖于第一个模板参数(也是函数参数本身的类型)的返回类型,可以在函数体内自由使用。

template <typename iTellYouWhetherFrankyIsAnIntegral, 
          typename Franky = std::conditional_t<std::is_integral_v<iTellYouWhetherFrankyIsAnIntegral>, uint32_t, float>>
Franky theFrankyFunction(iTellYouWhetherFrankyIsAnIntegral v) {
    return Franky();
}

希望这些翻译对您有所帮助。

英文:

The example you linked and took inspiration from aims at enabling a function using SFINAE (Substitution Failure Is Not An Error) on the return type. std::enable_if, if not instructed otherwise, resolves to a type that, if the substitution succeeded, contains a type field which is an alias for the void type. In essence, the return type of the example function is just void, if the substitution succeeds, otherwise the function gets discarded and compilation proceeds (SFINAE).

Reading your question, I had understood you want to replicate that behavior on your class constructors, that is enable/disable them according to the type of the constructor's argument, in which case the syntax that would make it work could be the following.

#include &lt;type_traits&gt;

struct T
{
    enum { int_t, float_t } type;
 
    template&lt;typename Integer,
             std::enable_if_t&lt;std::is_integral_v&lt;Integer&gt;&gt;* = nullptr&gt;
    T(Integer) : type(int_t) {}
 
    template&lt;typename Floating,
             std::enable_if_t&lt;std::is_floating_point_v&lt;Floating&gt;&gt;* = nullptr&gt;
    T(Floating) : type(float_t) {}
};

std::enable_if_t&lt;std::is_floating_point_v&lt;Floating&gt;&gt;* will expand to void* if the substitution succeeds, and therefore the template parameter will be pointer to void, that is a "non-type" parameter, which we default to nullptr. It works, compared to your previous example, because the non-type template parameter could have any other value, whilst void would be the same in both of your constructors in your attempt, hence the compiler would see that as a redefinition.

But by reading your comments to your own question, I think I understood what you really want to achieve.

The following function, named the way you named it in your comment, has a return type with the name Franky that depends on the first template parameter (which is also the type of the function argument itself) and can be freely used within the function's body.

template &lt;typename iTellYouWhetherFrankyIsAnIntegral, 
          typename Franky = std::conditional_t&lt;std::is_integral_v&lt;iTellYouWhetherFrankyIsAnIntegral&gt;, uint32_t, float&gt;&gt;
Franky theFrankyFunction(iTellYouWhetherFrankyIsAnIntegral v) {
    return Franky();
}

huangapple
  • 本文由 发表于 2023年7月14日 00:01:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/76681352.html
匿名

发表评论

匿名网友

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

确定