【问题标题】:Template parameter pack deduction when not passed as last parameter不作为最后一个参数传递时的模板参数包扣除
【发布时间】:2017-08-29 20:45:55
【问题描述】:

考虑以下代码:

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

现在让我们做一个小改动——让我们删除从本地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 安装进行测试。

问题:

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

【问题讨论】:

  • @Rakete1111 - 在我看来问题 1 是重复的;但是(恕我直言)问题 2 不是。
  • @max66 这两种情况都适用于提供的解决方法 (*testfunc)(sfunc)
  • @ArtemyVysotsky - 你的意思是巴里提出的解决方案吗?我没看到。精彩的!你(和 Rakete1111)是对的:重复。

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


【解决方案1】:

我们可以阻止扣除:

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 是不对的。但我还没有标准的研究来证实这一点。

【讨论】:

  • 不错的解决方案,与其他问题 (stackoverflow.com/questions/45318502/…) 中建议的解决方案不同,它适用于 MSVC。你能解释一下为什么将类型包装在本质上是std::identity 会禁用推论吗?
  • 我会向甜甜圈打赌,当 gcc 和 clang 不同意 MSVC 时,MSVC 是不对的。它比你的好答案更值得 +1。 :-)
  • @szcur 因为标准规定这样的类型映射会导致非推断上下文。我怀疑是因为一般情况允许图灵完全类型映射,而反转图灵完全映射是不切实际的。他们没有花费大量工作来描述映射的可逆子集,而是说“没有推论”
【解决方案2】:

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

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

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

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

@Yakk 在his answer 中所做的就是说参数类型是某个模板的成员。那时编译器会关闭自动推导,因为它无法从成员中推导出模板参数:

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

那里,语言的规则只允许通过完整的模板参数列表来获取argument的类型,无法推断。最终类型 typename block&lt;...&gt;::type 恰好是 std::function&lt;void(int, Args..., char)&gt;,但编译器无法从该“结果”推导出生成它的模板参数。

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

【讨论】:

    猜你喜欢
    • 2017-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-13
    • 2023-03-26
    • 2015-09-07
    • 2019-11-16
    • 1970-01-01
    相关资源
    最近更新 更多