【问题标题】:Why Won't Visual Studio let me use a Templatized, constexpr Function in enable_if?为什么 Visual Studio 不允许我在 enable_if 中使用模板化的 constexpr 函数?
【发布时间】:2017-01-11 14:36:10
【问题描述】:

所以我将其归结为最小、完整、可验证的示例,似乎 Visual Studio 2015 不允许我在 enable_if 中使用模板化的 constexpr 函数。

例如:

template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }

给我错误:

错误 C2995:enable_if&lt;_Test,T&gt;::type test(void): 函数模板已定义

当我尝试在替换中使用它时,失败不是这样的错误编译:

template <typename T>
enable_if_t<condition<T>()> test() { cout << "true\n"; }

template <typename T>
enable_if_t<!condition<T>()> test() { cout << "false\n"; }

这在 gcc 中运行良好:http://ideone.com/m9LDdS
如果我删除 condition 的模板化,它在 Visual Studio 2015 中可以正常工作。我相信constexpr函数是在中引入的,为什么Visual Studio 2015不支持这个?它是一个错误吗?

【问题讨论】:

标签: c++11 c++ c++11 templates visual-studio-2015 constexpr


【解决方案1】:

问题似乎是 MSVC14/VS2015 无法正确解析 SFINAE 表达式并结合 constexpr 函数的返回值作为模板参数。

作为一种解决方法,您可以将 constexpr 的返回值分配给结构的“静态 const”成员,并将该成员用作模板参数。

#include <type_traits>
#include <iostream>

using std::enable_if_t;
using std::cout;

template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }

template <typename T>
struct condition_ { static const bool value = condition<T>();};

template <typename T>
enable_if_t<condition_<T>::value> test() { cout << "true\n"; }
template <typename T>
enable_if_t<!condition_<T>::value> test() { cout << "false\n"; }

int main() {
    test<int>();
    test<bool>();
    return 0;
}

http://rextester.com/VVNHB62598


您在 cmets 中还提到您的实际问题出现在您的 MCVE (How can I Initialize a div_t Object?) 之外的另一个案例中

对于这种情况,解决方法可能如下所示:

#include <type_traits>
#include <cstdlib>
#include <utility>


template <typename T>
using divtype = decltype(std::div(std::declval<T>(), std::declval<T>()));


template <typename T>
struct condition
{
    static const bool value = divtype<T>{ 1, 0 }.quot != 0;
};


template <typename T>
std::enable_if_t<condition<T>::value, divtype<T>> make_div(const T quot, const T rem) { return{ quot, rem }; }

template <typename T>
std::enable_if_t<!condition<T>::value, divtype<T>> make_div(const T quot, const T rem) { return{ rem, quot }; }

int main() {

    make_div<int>(1, 2);
    return 0;
}

http://rextester.com/ULDFM22040


据此Visual Studio C++ Team blog entry VS2015 尚不(完全)支持Expression SFINAE

[1] 我们计划在 2015 RTM 之后立即开始在编译器中实现 Expression SFINAE,并且我们计划在 2015 年的更新中提供它,支持生产使用。 (但不一定是 2015 Update 1。可能需要更长时间。)

【讨论】:

  • 就像对我的回答的评论:在大多数情况下,您甚至不需要 constexpr 函数,只能使用 struct 。在上面的示例中,您可以轻松地将 static const bool value = condition&lt;T&gt;(); 替换为 static const bool value = sizeof(T) &gt; 1; 并摆脱 constexpr 函数。我会说这取决于用例和个人喜好到这里的方式。
  • 我永远不会相信有 decltype 的 MSVC SFINAE 案例作为决定是否失败的一部分;我见过很多错误,它“缓存”错误的结果并获得完全疯狂的行为。也许您的“通过模板类型”解决方案可以避免这种情况,但我希望在信任它之前进行广泛的测试。 “不完全支持”基本上意味着“它以不可预测的方式出错,而不是因错误而失败”。太可怕了。
  • @Yakk 很高兴知道。在这种情况下,divtype 应该为std::div 的 3 个重载提供 3 个特化
【解决方案2】:

问题是你最终得到了两个不同的模板test

template<class>void test()

并且编译器抱怨。这可能与表达式 SFINAE 失败有关,其中它没有“尽早”评估 condition&lt;T&gt;() 的表达式,或者以其他方式失败。

这是一个解决方法:

template<std::size_t>
struct counter{ enum type{}; };

template<std::size_t N>
using counter_type=typename counter<N>::type;

template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }

template <class T,counter_type<0>...,class=std::enable_if_t<condition<T>()>>
void test() { std::cout << "true\n"; }

template <class T,counter_type<1>...,class=std::enable_if_t<!condition<T>()>>
void test() { std::cout << "false\n"; }

现在两个不同的test 的模板签名不同,condition&lt;T&gt;() 的表达式 SFINAE 评估不完全正确不会导致问题。

【讨论】:

  • 你能帮我理解counter_type在模板参数中做了什么吗?它似乎与其他模板参数完全无关。也许这就是重点?
  • @JonathanMee 保证他们有不同的模板签名
  • 据我了解:counter_type&lt;0&gt;counter_type&lt;1&gt; 不同,这使得这两个模板函数“真正”不同。由于它是可变参数(参见省略号),因此您不必在此位置传递任何内容。它只是帮助编译器区分这两个函数。
  • @SimonKraemer 是的,这也是我得到的。当 SFINAE 应该已经工作时,这似乎是一个相当高的箍:(
【解决方案3】:

@Yakk 回答的另一种选择:

template <class T, std::enable_if_t<(sizeof(T) > 1)>* = nullptr>
void test() { std::cout << "true\n"; }

template <class T, std::enable_if_t<!(sizeof(T) > 1)>* = nullptr>
void test() { std::cout << "false\n"; }

编辑:刚刚看到这基本上是 TC 在 cmets 中指向的解决方案。

EDIT2:更正了要在 MSVC2015 中编译的代码,请参阅 cmets。

【讨论】:

  • 你能帮我理解一下吗?因此,如果enable_if 成功,我们将编译template &lt;class T, void* = nullptr&gt; void test() { cout &lt;&lt; "true\n"; },模板参数中的赋值对我来说没有任何意义。
  • @JonathanMee:见here。有趣的是,给出答案的也是 Yakk :-)
  • 这真的有效吗? Rextester 不喜欢它:rextester.com/ZCLIE12472
  • @SimonKraemer:感谢您的评论。 coliru does (它在 C++11 之前也是如此 afai 记得),而且这是一个基本技巧......不知道为什么 rextester 没有。
  • @Yakk:好像是这样。 (无论如何我想我记得它在 MSVC2015 中对我有用,但也许我错了)
【解决方案4】:

这是一个已知的 MSVC 问题,在他们的一篇博文中提到过。发生了什么是编译器无法识别 SFINAE 使test() 的第二个版本与第一个不同,它需要一点提示;一个简单的虚拟参数就足够了,它可以区分两个版本。

#include <type_traits>
#include <iostream>
using std::enable_if_t; using std::cout;

template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }

#ifdef    _MSC_VER
    #define MSVC_DUMMY int /*msvc_dummy*/ = 0
#else  // _MSC_VER
    #define MSVC_DUMMY
#endif // _MSC_VER

template <typename T>
enable_if_t<condition<T>()> test() { cout << "true\n"; }

template <typename T>
enable_if_t<!condition<T>()> test(MSVC_DUMMY) { cout << "false\n"; }

int main() {
    test<char>();
    test<int>();
}

这个works on MSVC,只需对代码进行少量修改。一旦他们最终在没有提示的情况下让它工作,它也很容易删除。


如果需要一致的界面,可以将其隐藏在辅助函数后面。

template <typename T>
enable_if_t<condition<T>()> test_() { cout << "true\n"; }

template <typename T>
enable_if_t<!condition<T>()> test_(MSVC_DUMMY) { cout << "false\n"; }

template <typename T>
auto test() { return test_<T>(); }

【讨论】:

  • 很遗憾,我不记得具体是在哪篇博文中提到的,所以我无法提供链接。
猜你喜欢
  • 2020-02-05
  • 2017-12-29
  • 1970-01-01
  • 1970-01-01
  • 2013-09-05
  • 2017-05-27
  • 2015-02-19
  • 2019-05-20
  • 2019-05-04
相关资源
最近更新 更多