【问题标题】:Shouldn't the compiler be able to deduce the template arguments from the concept?编译器不应该能够从概念中推断出模板参数吗?
【发布时间】:2021-02-18 16:49:29
【问题描述】:

在下面这段代码中,编译器不应该能够从概念中推断出模板参数吗?

#include <iostream>

template<typename T>
struct Bar;

template<typename F, typename T>
concept Foo = requires (F foo, Bar<T> bar)
{
    { foo.test(bar) } -> std::same_as<T>;
};

template<typename T>
struct Bar
{
    T value;

    template<Foo<T> F>
    T test(F foo)
    {
        return foo.test(*this);
    }
};

template<typename T>
struct FooBar
{
    T value;

    T test(Bar<T> bar)
    {
        return (value = bar.value) + bar.value;
    }
};

int main(void)
{
    Bar<float> b{ 1 };
    std::cout << b.test(FooBar{}) << std::endl; // Fails to deduce the template argument
    std::cout << b.test(FooBar<float>{}) << std::endl; // Compiles successfully
}

这是一个 MSVC 和 GCC 尚未实现的功能(两者都无法编译代码)吗?还是只是无效的 C++20 代码?如果是这样,为什么? float 不是模板参数T 唯一可能的类型吗?或者是否有一些我遗漏的替代方法可能会导致编译器产生歧义?

【问题讨论】:

  • 如果您让我们知道错误消息是什么以及它发生的位置会很好。

标签: c++ templates c++20 template-argument-deduction c++-concepts


【解决方案1】:

编译器究竟是从什么推导出FooBar{} 的模板参数?你没有提供任何参数,也没有特殊的类模板推导指南,模板FooBar没有默认模板参数。

如果您希望通过表达式的使用而不是表达式本身的固有属性(初始化程序中的参数,默认模板参数)进行模板推导,那么就是错误的期望。

类模板参数推导完全基于提供给类对象的初始化程序的参数,就像函数模板参数推导完全基于提供给函数的参数一样。此后如何使用这两种表达方式都无关紧要。

【讨论】:

  • 你是绝对正确的。我不知道为什么,但我确信模板推导可以通过使用表达式来完成。它仍然困扰着我,这是编译器无法做到的(或者它仍然不是 c++ 功能),因为在很多情况下没有歧义,编译器应该能够确定它,对吗?无论如何,谢谢你的回答!
【解决方案2】:

在这种情况下,编译器不会推断类型,因为在使用时,必须为模板类型定义具体(非泛型)类型。

默认模板参数

在提供的示例代码中,在编译器抱怨的地方,FooBar{} 缺少具体的类型定义,如您所知道的 FooBar&lt;SomeType&gt;

这种语法FooBar{} 可以与默认模板参数一起使用,如下所示:

template<typename T=float> // <-- Add a default concrete type, now it compiles and runs
struct FooBar
...

值类型别名

另一种选择是在使用点创建一个别名,捕获通用 value_type,然后将其应用于模板。

template<typename T>
struct Bar
{
    using value_type = T; // <-- Allows capturing the value_type after the 'point of use' definition.

    T value;

    template<Foo<T> F>
    T test(F foo)
    {
        return foo.test(*this);
    }
};

int main(void)
{
    using V = Bar<float>::value_type; 

    Bar<V> b{ 1 };
    std::cout << b.test(FooBar<V>{}) << std::endl; // Compiles successfully
    std::cout << b.test(FooBar<V>{}) << std::endl; // Compiles successfully
}

这很有趣,因为从某种意义上说,接收通用对象的函数可以查看该对象的 value_type 并在实例化新的通用对象时转发它。

concept 的定义似乎是正确的,如下代码所示:

Bar<float> b;
b.test(FooBar<int>{});

编译器在测试时发出以下消息:

错误 C7602: 'Bar::test': 不满足相关约束

根据概念实现的逻辑,这是预期的。

总结

  • 由于 FooBar 是一个模板,并且与 Bar 也是一个模板这一事实完全没有关系,它仍然需要在使用时指定自己的类型,或者可以为模板指定默认 value_type论据。

  • 通用对象的 value_type 实际上可以在使用点之后使用别名捕获并在定义其他通用对象时应用。

参考

我发现 Bjarne Stroustrup 的 A Tour of C++ 中关于 模板 的第 6 节和关于 模板编译模型 的第 7.5 节提供了非常丰富的信息这些主题,也可能让您有更深入的了解。

注意

对于那些愿意在 Visual Studio 2019 中启用 C++20 预览功能的用户,请访问:

Project &gt; YourProjectName Properties &gt; C/C++ &gt; Language &gt; C/C++ Language Standard

然后选择:

Preview - Features from the Latest C++ Working Draft(/std:c++latest)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-26
    • 1970-01-01
    • 2015-09-23
    • 2019-11-23
    相关资源
    最近更新 更多