【问题标题】:Null as default template argumentNull 作为默认模板参数
【发布时间】:2012-11-23 08:07:46
【问题描述】:

为什么不能在模板函数中使用 NULL 作为默认指针参数? 让我们考虑以下代码:

template<class Graph, class NodeAttribs, class ArcAttribs> string
graphToGraphviz(Graph       &graph,
                NodeAttribs *nattribs = NULL,
                ArcAttribs  *aattribs = NULL,
                string      name      = ""){
   /*...*/
}

我希望能够这样称呼它:

graphToGraphviz(g);

我怀疑,编译器认为它无法解析 NULL 的类型,但如果属性为 NULL(存在 if 条件),则不会使用这些类型。但也许这种情况无法通过编译器以正确的方式解决。如果是,我该如何编写这样的重载函数,这将允许我使用短格式?

我有一个像这样重载它的想法:

class Empty{}

template<class Graph> string
graphToGraphViz(Graph       &graph,
                string      name      = ""){
    return graphToGraphviz<Graph, Empty, Empty>(graph, NULL, NULL, name)
}

但是编译器给了我错误,除其他外,那个类 Empty 没有定义operator []。这又是不稳定的,但是我是否必须使所有这些“虚拟”运算符重载和空函数来满足编译器的要求,或者有更好的方法吗?

编辑: 请查看完整的源代码 - 它将 Lemon 图转换为 graphviz 格式: 我尝试使用 C++11 中的新语法(如下面的答案所示),但没有成功。

#ifndef GRAPHTOGRAPHVIZ_H_
#define GRAPHTOGRAPHVIZ_H_

#include <lemon/list_graph.h>

using namespace lemon;
using namespace std;

/* USAGE:
 * ListDigraph::NodeMap<unordered_map<string, string>> nodeAttribs(g);
 * ListDigraph::ArcMap<unordered_map<string, string>> arcAttribs(g);
 * nodeAttribs[node]["label"] = "node_label";
 * string dot = graphToGraphviz(g, &nodeAttribs, &arcAttribs, "hello");
 */

template<class Map>
string getAttribs(Map &map){
    string attribs = "";
    for (const auto &el : map){
        if (el.second != "")
            attribs += "\"" + el.first + "\"=\"" + el.second + "\",";
    }
    if (attribs != "")
        attribs = " [" + attribs + "]";
    return attribs;
}


template<class Graph, class NodeAttribs, class ArcAttribs> string
graphToGraphviz(Graph       &graph,
                NodeAttribs *nattribs = NULL,
                ArcAttribs  *aattribs = NULL,
                string      name      = ""){

    typedef typename Graph::template NodeMap<string> NodeMap;
    typedef typename Graph::NodeIt NodeIterator;
    typedef typename Graph::ArcIt  ArcIterator;

    NodeMap labels(graph);
    ostringstream layout;
    layout << "strict digraph \""+name+"\" {\n";

    // prepare labels
    for (NodeIterator node(graph); node != INVALID; ++node){
        string label = "";
        if (*nattribs != NULL)
            label = (*nattribs)[node]["label"];
        if (label == "") label = static_cast<ostringstream*>( &(ostringstream() << graph.id(node)) )->str();
        label = "\"" + label + "\"";
        labels[node] = label;
    }

    // initialize nodes
    for (NodeIterator node(graph); node != INVALID; ++node){
        layout << labels[node];
        if (*nattribs != NULL)
            layout << getAttribs((*nattribs)[node]);
        layout << ";" << std::endl;
    }

    // initialize arcs
    for (ArcIterator arc(graph); arc != INVALID; ++arc){
        layout << labels[graph.source(arc)] << "->" << labels[graph.target(arc)];
        if (*aattribs != NULL)
            layout << getAttribs((*aattribs)[arc]);
        layout << ";" << std::endl;
    }
    layout << "}";
    return layout.str();
}


#endif /* GRAPHTOGRAPHVIZ_H_ */

使用 C++11 语法,函数头将如下所示:

template<class Graph, class NodeAttribs=ListDigraph::NodeMap<string>, class ArcAttribs=ListDigraph::NodeMap<string> > string
graphToGraphviz(Graph       &graph,
                NodeAttribs *nattribs = NULL,
                ArcAttribs  *aattribs = NULL,
                string      name      = "")

但它不能编译并给出大量奇怪的错误。

【问题讨论】:

  • 您对Empty 类的想法可能是最简单的。您也可以使用部分特化,但这可能会过大,具体取决于代码的复杂性。
  • @rodrigo:你不能部分特化一个函数。
  • @n.m.:哦,对了!但是,您可以使用某种 traits 类并部分专门化这些...

标签: c++ templates c++11 overloading default


【解决方案1】:

调用时编译器出现问题:

graphToGraphviz(g);

现在NodeAttribsArcAttribs 的类型是什么?
无论您是否使用它,编译器都必须推断出它的类型。因为使用或不使用是运行时检查。
使用您当前的代码,上述类型变得不可推断

我怎么能写出这样的重载函数

你的问题有答案了!!
重载template函数,从你的原始模板函数中删除默认参数,让这两个函数共同存在:

template<class Graph>
string graphToGraphviz(Graph &graph, string name = "");

【讨论】:

  • 当然这样的重载是最简单的方法,但是想象一下代码,它几乎是不可分割的,我想从我的重载中调用基本函数。然后我又面临这个问题了。
  • @danilo2 不,你不是。在重载的情况下,您可以显式指定模板参数,例如:return graphToViz&lt;Graph, void, void&gt;(graph, NULL, NULL, name);
  • 是的,当然,但是你遇到了我问的同样的问题 - 在编译 return graphToViz&lt;Graph, void, void&gt;(graph, NULL, NULL, name); 期间,编译器抛出了他无法解析 NULL 模板类型的错误。
【解决方案2】:

如果你使用的是 C++11,你可以这样做:

template<class Graph, class NodeAttribs=Empty, class ArcAttribs=Empty> ...

我没有在标准中找到相关的语言,但是gcc接受了。

【讨论】:

  • 我已经尝试过这个版本,但它不起作用 - 请查看我附加到我的问题的源代码。
  • 函数体有错误。您还需要使用有效的默认参数,而不是Empty,而是真正的属性映射。 This 为我编译 g++-4.6.1 -std=c++0x
【解决方案3】:

你试过了吗

template<class Graph, class NodeAttribs, class ArcAttribs> string
graphToGraphviz(Graph       &graph,
                NodeAttribs *nattribs = (<NodeAttribsClass>*)NULL,
                ArcAttribs  *aattribs = (<ArcAttribsClass>*)NULL,
                string      name      = ""){
   /*...*/
}

template<class Graph, class NodeAttribs = NodeAttribsClass, class ArcAttribs = ArcAttribsClass> string
graphToGraphviz(Graph       &graph,
                NodeAttribs *nattribs = NULL,
                ArcAttribs  *aattribs = NULL,
                string      name      = ""){
   /*...*/
}

NodeAttribsClassArcAttribsClass 是可以在该插槽中使用的有效(具体)类?

【讨论】:

  • 我试过这个版本,但它对我不起作用 - 请看一下我添加到第一篇文章的源代码。
猜你喜欢
  • 2016-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-28
  • 1970-01-01
相关资源
最近更新 更多