基于模板值的条件编译?

11

这个问题来源于 模板中的类型条件

这个问题非常相似,但原始问题并没有得到完全回答。

#include "stdafx.h"
#include <type_traits>


class AA {
public:
    double a;

    double Plus(AA &b) {
        return a + b.a;
    }
};

template<class T> double doit(T &t) {
    if (std::is_same<T, AA>::value)
        return t.Plus(t);
    else
        return t + t;
}

int _tmain(int argc, _TCHAR* argv[])
{
    double a;
    AA aa;

    doit(a);
    doit(aa);

    return 0;
}

这段代码无法编译,我也没有期望它能够编译。但是,有没有可能根据模板值来选择性地编译某些代码而不编译其他代码呢?在这里,类型“double”没有名为“Plus”的方法,类“AA”也没有重载“+”运算符。考虑到操作的微妙语义,运算符重载并不总是可取的,因此我正在寻找一种替代方案。我更喜欢使用#ifdef(在引用的问题中提出的真正条件编译),但是基于模板值。


1
我发现了这篇文章,我非常喜欢它。 - Andrew
5个回答

13

自C++17起,引入了静态if,也称为if-constexpr。由于clang-3.9.1和gcc-7.1.0已经支持该特性,因此以下代码可以编译通过。最新的MSVC编译器19.11.25506也可以很好地处理,只需添加/ std:c ++ 17选项即可。

template<class T> double doit(T &t) {
    if constexpr (std::is_same_v<T, AA>)
        return t.Plus(t);
    else
        return t + t;
}

天啊,如果这是唯一的选择,我会很讨厌。如果模板类型属于某种类型,我需要向我的构造函数添加一个额外的参数。我相信编译器只编译了这段代码的一个版本,但它看起来像一个典型的“if”语句,你肯定不能用它来包装函数。 - Jonathan Wood
@JonathanWood https://dev59.com/f5bfa4cB1Zd3GeqPycRQ#37048889 - Andrew

5
您需要的是静态if。C++没有这个功能。有许多解决方法,但没有一个能像本机支持那样好。除了其他两个答案中指定的方法之外,您还可以尝试标签分派。
template<class T> double doitImpl(T &t, std::true_type) {
    return t.Plus(t);
}

template<class T> double doitImpl(T &t, std::false_type) {
    return t+t;
}

template<class T> double doit(T &t) {
    return doitImpl(t, std::is_same<T, AA>);
}

2
谢谢,我认为这是最好的解决方案。静态if将是理想的方法。你看,这是一个简化版的更大问题,其中有4个专业化需要使用代码的一种变体,而另外4个专业化则需要第二种变体的代码。这个解决方案让我在代码库中拥有每个变体的一个实现。 - Rob Bryce
来到这里时也有同样的问题。这个答案最合适。谢谢!真的很想看到它被接受。 - Wormer
更多关于该主题的研究表明,自C++17以来我们拥有了if-constexpr。已经为此添加了一个答案。 - Wormer

3
double doit(AA &t) {
  return t.Plus(t);;
}
template<class T> double doit(T &t) {
  return t + t;
}

你的代码无法工作,因为如果模板被推断为AA,那么正文中的t + t是不合法的。另一方面,如果T被推断为double,则t.Plus(t)就变得不合法了。
为了更好地理解发生了什么: 每个调用模板类型的模板都会实例化一次。 doIt(a)使用T = double实例化doIt
double doIt<double>(double &t) {
  if (false)
     return t.Plus(t);  // <-- syntax error
  else
    return t + t;
}

doIt(aa)实例化了T = AAdoIt函数:

double doIt<AA>(AA &t) {
  if (true)
    return t.Plus(t);
  else 
    return t + t;   // <-- syntax error
}

为避免函数重载,您应该避免专门化函数模板。您可以阅读这篇优秀的Herb Sutter文章:为什么不专门化函数模板?


3

重载?

template<class T>
double doit(T &t) {
    return t + t;
}

double doit(AA &t) {
    return t.Plus(t);
}

明确的特化也是可能的,虽然多余:

template<class T>
double doit(T &t) {
    return t + t;
}

template<>
double doit<>(AA &t) {
    return t.Plus(t);
}

实际上,专业化并不像你期望的那样工作:http://www.gotw.ca/publications/mill17.htm - Răzvan Cojocaru
@RazvanCojocaru,您发表此评论的特定原因是什么?我了解显式特化周围的细微差别,因此它的工作方式与我预期的相当一致。在这种情况下,它的工作效果也正如预期的那样。 - Columbo
代码即使进行微小的修改也无法正常工作。Sutter的文章对此进行了解释。例如,以下代码将打印出“base template”:`#include <iostream>using namespace std;struct X {};template<class T> void doit(T) { cout << "base template\n"; }template<> void doit<>(X&) { cout << "full specialization\n"; }int main() { X x; doit(x); } ` - Răzvan Cojocaru
@RazvanCojocaru 你刚刚改变了 doit 的含义,却没有相应地调整显式特化的含义。这是一派胡言。 - Columbo

2

这段代码可以编译并运行:

#include <boost/type_traits/is_floating_point.hpp>
#include <boost/type_traits/is_class.hpp>
#include <boost/utility/enable_if.hpp>

using namespace boost;

class AA {
public:
    double a;

    AA Plus(AA &b) {
        AA _a;
        _a.a = a + b.a;
        return _a;
    }
};


template<class T>
typename disable_if<is_floating_point<T>, T >::type
doit(T &t) {
    return t.Plus(t);
}


template<class T>
typename enable_if<is_floating_point<T>, T >::type
doit(T &t) {
    return t+t;
}

int _tmain(int argc, _TCHAR* argv[])
{
    double a;
    AA aa;

    doit(a);
    doit(aa);

    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接