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

时间:2017-08-29 20:45:56

标签: c++ c++11 templates variadic-templates

请考虑以下代码:

#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):

  

参数包不会出现在参数的最后   声明,它是一个非推断的上下文。非推断的上下文意味着   必须明确给出模板参数。

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)>’

现在让我们稍作改动 - 让从我们的本地int删除 func参数,从而导致模板参数包变空了:

#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安装进行测试。

问题:

  1. 第一种情况谁是正确的?
  2. 如何达到预期效果 - 即,如何明确指定(可能为空)参数包?

2 个答案:

答案 0 :(得分:6)

我们可以阻止扣除:

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是不对的。但我没有标准钻探确认这一点。

答案 1 :(得分:1)

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

我不想专注于编译器是否错误,因为不是参数列表中最后一个的类型包非常难以使用,所以,我宁愿专注于将它们出现的情况踩到一边。 / p>

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

回避问题的一种方法是禁用自动调解并明确提供模板参数。

@Yakk在his answer中的作用是说参数类型是某个模板的成员。此时,编译器会关闭自动调整,因为它无法从成员中推断出模板参数:

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

在那里,语言规则只允许通过拥有完整的模板参数列表来获取argument的类型,没有可能的推论。最终类型typename block<...>::type恰好是std::function<void(int, Args..., char)>,但编译器不能从“结果”转到推导出产生它的模板参数。

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