【问题标题】:Forward declaration of template class in nested namespace: where should default template arguments go?嵌套命名空间中模板类的前向声明:默认模板参数应该去哪里?
【发布时间】:2013-09-03 18:57:16
【问题描述】:

我在嵌套命名空间中有一个模板类的前向声明

namespace n1
{
    namespace n2
    {
        template <typename T, typename S>
        struct A;
    }
    using n2::A;
}

后面是一个定义,实际上是在不同的文件中,中间有一些东西:

struct X { };

namespace n1
{
    namespace n2
    {
        template <typename T, typename S = X>
        struct A { };
    }
    using n2::A;
}

那么以下总是可以的:

n1::n2::A <int> a;

但是这个快捷方式

n1::A <int> a;

在 clang 中给出一个编译错误

error: too few template arguments for class template 'A'

除非我删除前向声明; g++ 两者都接受。

clang 似乎保留了第一个不包含默认模板参数的声明(我不能包含它,因为我还没有定义 X)。

如果我使用单个命名空间没有问题(但这不是解决方案)。

我做错了什么,或者哪个编译器是正确的?快捷方式如何与前向声明和嵌套命名空间一起使用?我都需要。

前向声明X + S 的默认参数当然可以,但是太繁琐(实际上有几十个,整个文件结构会改变)。

【问题讨论】:

  • 中间有东西并且在不同文件中的事实对于clang来说并不重要。 Live example
  • 根据 [temp.param]/10,此问题与模板无关,因为该机制是根据函数默认参数定义的。 Live example
  • 这可能比我最初想象的要复杂一些。 [dcl.fct.default]/9 实际上说您的代码应该可以工作,但是有一个带有 using-declarationopen issue,其中建议的修复会与本段产生矛盾并会破坏您的代码.
  • 我认为这个问题的建议解决方案中的第 3 点回答了这个问题(不幸的是,clang 是正确的)但也给出了一个想法:跳过 n1 中的第一个 using-declaration。可能在另一个命名空间中进行使用声明来做任何需要的事情,最后在定义之后在 n1 中进行使用声明。谢谢!
  • @iavr 在下面看到我的回答,GCC 也不接受它src/main.cpp:26:17: error: wrong number of template arguments (1, should be 2) GCC ftw btw。

标签: c++ templates namespaces forward-declaration


【解决方案1】:

我发现这是一种最方便的解决方法:

namespace n1
{
    namespace n2
    {
        template <typename T, typename S>
        struct A;
    }

    namespace fwd { using n2::A; }
}

// stuff on n1::fwd::A;

struct X { };

namespace n1
{
    namespace n2
    {
        template <typename T, typename S = X>
        struct A { };
    }

    using n2::A;
}

也就是说,将第一个 using 声明移动到另一个命名空间。现在“东西”可能是这样的:

namespace n1
{
    namespace stuff
    {
        using namespace fwd;

        template <typename T>
        struct R /*...*/;

        template <typename T, typename S>
        struct R <A <T, S> > /*...*/;
    }
}

使用没有任何命名空间的A。我的项目中只有一个n1,但有很多n2,因此在单个命名空间n1::fwd 中向前移动所有使用声明更方便,因为我不能只使用n1

【讨论】:

    【解决方案2】:

    template&lt;class T&gt; using myname = some::templatething&lt;T&gt;;

    然后你可以使用我的名字

    在您的情况下,在您的 n1 中粘贴 template&lt;class T,class S=X&gt; using A = n2::A&lt;T,S&gt;;

    刚刚写了一个与此Symbol not found when using template defined in a library 相关的答案,顺便说一句,请阅读。

    好的,还没有打勾,所以我会再帮忙!

    不会编译

    #include <iostream>
    
    namespace n1 {
    namespace n2 {
    template<class U,class V> struct A;
    }
    template<class U,class V> using A = n2::A<U,V>;
    }
    
    static n1::A<int,int>* test;
    
    struct X {};
    namespace n1 {
    namespace n2 {
    template<class U,class V> struct A {};
    }
    template<class U,class V=X> using A = n2::A<U,V>;
    }
    
    static n1::A<int> test2;
    
    
    int main(int,char**) {
    
        return 0;
    }
    

    为什么? C++的“先声明规则”

    这是编译器的输出:

    make all 
    if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
    g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -Isrc -c src/main.cpp -o build/main.o
    src/main.cpp:26:17: error: wrong number of template arguments (1, should be 2)
     static n1::A<int> test2;
                     ^
    src/main.cpp:13:47: error: provided for ‘template<class U, class V> using A = n1::n2::A<U, V>’
     template<class U,class V> using A = n2::A<U,V>;
                                                   ^
    src/main.cpp:26:24: error: invalid type in declaration before ‘;’ token
     static n1::A<int> test2;
                            ^
    src/main.cpp:16:24: warning: ‘test’ defined but not used [-Wunused-variable]
     static n1::A<int,int>* test;
                            ^
    src/main.cpp:26:19: warning: ‘test2’ defined but not used [-Wunused-variable]
     static n1::A<int> test2;
                       ^
    make: *** [build/main.o] Error 
    

    好吧,这是在抱怨未使用的变量,这很公平,但请注意它引用了第 13 行,这是因为它使用了第一个定义,默认模板参数非常原始,我现在无法引用规范。 https://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fdefault_args_for_templ_params.htm

    这可能会提供一些见解。

    无论如何请注意:

    这样编译

    #include <iostream>
    
    namespace n1 {
    namespace n2 {
    template<class U,class V> struct A;
    }
    template<class U,class V> using A = n2::A<U,V>;
    }
    
    static n1::A<int,int>* test;
    
    struct X {};
    namespace n1 {
    namespace n2 {
    template<class U,class V> struct A {};
    }
    template<class U,class V=X> using B = n2::A<U,V>;
    }
    
    static n1::B<int> test2;
    
    
    int main(int,char**) {
    
        return 0;
    }
    

    因为 B 没有事先定义。

    构建输出

    make all 
    if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
    g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -Isrc -c src/main.cpp -o build/main.o
    src/main.cpp:16:24: warning: ‘test’ defined but not used [-Wunused-variable]
     static n1::A<int,int>* test;
                            ^
    src/main.cpp:26:19: warning: ‘test2’ defined but not used [-Wunused-variable]
     static n1::B<int> test2;
                       ^
    g++  build/main.o  -o a.out
    

    看,很好:)

    记住,前向声明只能使用 *s 和 &s(因为它们的大小是已知的,实际上是固定大小的) - 哦和 &&s

    所以你需要马上把那个默认设置进去!

    【讨论】:

    • 谢谢,这样可以避免在 n1 完成之前使用真正的 A,并使用不同的 name 进行 using 声明;但我觉得太复杂了。目前,我已经删除了前向声明并在 A 完成后声明了“东西”,我认为这是更好的设计。如果将来我真的需要它,并且正如我在问题下方的评论中所写,我宁愿将 using-declaration 移动到另一个命名空间而不是重命名它,从而完全避免模板参数 - 我将添加一个答案那个。
    • 此问题与 OP 不同,因为您使用的是别名模板声明 (template &lt; /*...*/ &gt; using X = /*...*/;),而 OP 使用的是“简单”(非模板)别名声明。效果不同(clang++ 和 g++ 都拒绝它),这根本与命名空间无关 -> Live example。这可能与open issue 1349有关。