当前位置: 首页 > 知识库问答 >
问题:

未作为最后一个参数传递时的模板参数包扣除

孙梓
2023-03-14

考虑以下代码:

#include <iostream>
#include <functional>

template<typename... Args>
void testfunc(const std::function<void (float, Args..., char)>& func)
{

}

int main(int argc, char* argv[])
{
    auto func = [](float, int, char) {};
    auto sfunc = static_cast<std::function<void (float, int, char)>>(func);
    testfunc<int>(sfunc);

    return 0;
}

我显式指定类型是因为(https://stackoverflow.com/a/40476083):

当参数包没有最后出现在参数html" target="_blank">声明中时,它是一个非推断上下文。非推断上下文意味着必须显式给出模板参数。

MSVC成功编译,而gcc和clang都拒绝代码:

source_file.cpp: In function ‘int main(int, char**)’:
source_file.cpp:14:24: error: no matching function for call to ‘testfunc(std::function<void(float, int, char)>&)’
     testfunc<int>(sfunc);
                        ^
source_file.cpp:5:6: note: candidate: template<class ... Args> void testfunc(const std::function<void(float, Args ..., char)>&)
 void testfunc(const std::function<void (float, Args..., char)>& func)
      ^
source_file.cpp:5:6: note:   template argument deduction/substitution failed:
source_file.cpp:14:24: note:   mismatched types ‘char’ and ‘int’
     testfunc<int>(sfunc);
                        ^
source_file.cpp:14:24: note:   ‘std::function<void(float, int, char)>’ is not derived from ‘const std::function<void(float, Args ..., char)>’

现在让我们做一个小小的改变-我们从本地func中删除int,从而使模板参数包变为空:

#include <iostream>
#include <functional>

template<typename... Args>
void testfunc(const std::function<void (float, Args..., char)>& func)
{

}

int main(int argc, char* argv[])
{
    auto func = [](float, char) {};
    auto sfunc = static_cast<std::function<void (float, char)>>(func);
    testfunc<>(sfunc);

    return 0;
}

这一次,三个编译器都认为代码不正确。使用http://rextester.com/l/cpp_online_compiler_gcc和本地Visual Studio安装进行了测试。

问题:

    < li >在第一种情况下,谁是正确的? < li >如何达到预期的效果,即如何显式指定一个(可能为空的)参数包?

共有2个答案

彭琛
2023-03-14

我怀疑标准说模板参数包的推导必须是“贪婪的”,这会使MSVC在接受格式错误的代码时出错。

我不想专注于编译器是否错误,因为不是参数列表中最后一个的类型包很难处理,所以我宁愿专注于出现它们的情况。

语言的规则规定,包中的显式模板参数只是包的开始,而不是整个包,这意味着自动删除规则将被激活。另一方面,模板参数的自动识别是“贪婪的”,无论以后需要满足什么,它都会继续进行。模板参数的自动识别不会“回溯”。

回避这个问题的一个方法是禁用自动推断并显式提供模板参数。

@Yakk在回答中所做的是说参数类型是某个模板的成员。此时,编译器将关闭自动推断,因为它无法从成员推断模板参数:

template<typename... Args>
void test_func(
    const typename block<
        std::function<void(int, Args..., char)>
    >::type &argument
);

在那里,语言的规则只允许通过拥有完整的模板参数列表来获取< code>argument的类型,不可能进行推导。最后一个类型< code>typename块

参数的类型是根据模板参数输入计算得出的。

沈弘文
2023-03-14

我们可以阻止扣除:

template<typename... Args>
void testfunc(const block_deduction<std::function<void (float, Args..., char)>>& func)

具有

template<class T>
struct tag_t{using type=T;};

template<class T>
using block_deduction=typename tag_t<T>::type;

现在Args...处于非推导的上下文中。

您可以使用SFINAE做一些更有趣的事情,并省略char,然后测试char是否位于Args...的末尾,但这似乎矫枉过正。

我敢跟甜甜圈打赌,当gcc和clang不同意MSVC时,MSVC是不对的。但我还没有标准地深入证实这一点。

 类似资料:
  • 在这种情况下,我总是希望从第一个参数中推导出来,但看起来我没有很好地表达这一点。的类型是,因此: 有没有一种方法可以声明,这样第二个参数就不参与模板参数的推导了?

  • 我有一个函数,它有两个参数,最后一个参数是回调闭包: 我想让第二个回调闭包参数可选。我试图将上面的函数重新定义为: 我收到编译器错误: nil默认参数值不能转换为类型'

  • 标准中似乎没有规则提到模板参数需要默认参数的情况。 在dcl中。fct。默认值#1 如果在参数声明中指定了初始化子句,则将此初始化子句用作默认参数。缺省参数将用于缺少尾随参数的调用。 在本节中,规则明确描述了何时为函数调用提供默认参数。但是,我在标准中没有找到与上面描述何时提供默认参数作为模板参数的语句类似的引用。 例如

  • 我目前有一个,但是为了灵活性,我希望能够分配一个lambda表达式,将作为映射中的值返回。 所以我创建了这个模板类: 并像这样使用它: IntelliSense提供了更多信息: 多个操作符“=”匹配这些操作数:function“valueorfunction::operator=(const std::function&other)[with T=std::wstring]”function“va

  • N4527 14.8.2.4[温度扣除部分] 3用于确定排序的类型取决于进行部分排序的上下文: (3.1)-在函数调用的上下文中,使用的类型是那些函数调用具有参数的函数参数类型。 (3.2)-在调用转换函数的上下文中,使用转换函数模板的返回类型。 (3.3)-在其他上下文(14.5.6.2)中使用函数模板的函数类型。 4以上从参数模板中指定的每种类型以及从参数模板中指定的相应类型都用作P和A的类型

  • 我有两个功能。一个是,它以Int x和函数p作为参数,并返回布尔结果p(x)。就这么简单。 然后我有第二个函数,它就像 在调用时,有没有办法更改,使其像?(如果f返回true,则将其设为false) 比如: 我知道我可以通过masterChecker调用,但我想知道的是,是否可以更改作为参数传递的函数行为。