[clang-tools-extra] r261737 - [clang-tidy] Added a check for forward declaration in the potentially wrong namespace
Sean Silva via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 24 19:59:08 PST 2016
This or r261737 seem to be causing a bot failure:
http://lab.llvm.org:8011/builders/llvm-clang-lld-x86_64-scei-ps4-windows10pro-fast/builds/636
-- Sean Silva
On Wed, Feb 24, 2016 at 5:35 AM, Alexander Kornienko via cfe-commits <
cfe-commits at lists.llvm.org> wrote:
> Author: alexfh
> Date: Wed Feb 24 07:35:32 2016
> New Revision: 261737
>
> URL: http://llvm.org/viewvc/llvm-project?rev=261737&view=rev
> Log:
> [clang-tidy] Added a check for forward declaration in the potentially
> wrong namespace
>
> Adds a new check "misc-forward-declaration-namespace".
> In check, A forward declaration is considerred in a potentially wrong
> namespace
> if there is any definition/declaration with the same name exists in a
> different
> namespace.
>
> Reviewers: akuegel, hokein, alexfh
>
> Patch by Eric Liu!
>
> Differential Revision: http://reviews.llvm.org/D17195
>
> Added:
>
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp
>
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h
>
> clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst
>
> clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp
> Modified:
> clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt
> clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp
> clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst
>
> Modified: clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt
> URL:
> http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt?rev=261737&r1=261736&r2=261737&view=diff
>
> ==============================================================================
> --- clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt (original)
> +++ clang-tools-extra/trunk/clang-tidy/misc/CMakeLists.txt Wed Feb 24
> 07:35:32 2016
> @@ -6,6 +6,7 @@ add_clang_library(clangTidyMiscModule
> AssignOperatorSignatureCheck.cpp
> BoolPointerImplicitConversionCheck.cpp
> DefinitionsInHeadersCheck.cpp
> + ForwardDeclarationNamespaceCheck.cpp
> InaccurateEraseCheck.cpp
> IncorrectRoundings.cpp
> InefficientAlgorithmCheck.cpp
>
> Added:
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp
> URL:
> http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp?rev=261737&view=auto
>
> ==============================================================================
> ---
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp
> (added)
> +++
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp
> Wed Feb 24 07:35:32 2016
> @@ -0,0 +1,174 @@
> +//===--- ForwardDeclarationNamespaceCheck.cpp - clang-tidy ------*- C++
> -*-===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
>
> +//===----------------------------------------------------------------------===//
> +
> +#include "ForwardDeclarationNamespaceCheck.h"
> +#include <stack>
> +#include <string>
> +#include "clang/AST/ASTContext.h"
> +#include "clang/AST/Decl.h"
> +#include "clang/ASTMatchers/ASTMatchFinder.h"
> +#include "clang/ASTMatchers/ASTMatchers.h"
> +
> +using namespace clang::ast_matchers;
> +
> +namespace clang {
> +namespace tidy {
> +namespace misc {
> +
> +void ForwardDeclarationNamespaceCheck::registerMatchers(MatchFinder
> *Finder) {
> + // Match all class declarations/definitions *EXCEPT*
> + // 1. implicit classes, e.g. `class A {};` has implicit `class A`
> inside `A`.
> + // 2. nested classes declared/defined inside another class.
> + // 3. template class declaration, template instantiation or
> + // specialization (NOTE: extern specialization is filtered out by
> + // `unless(hasAncestor(cxxRecordDecl()))`).
> + auto IsInSpecialization = hasAncestor(
> + decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()),
> + functionDecl(isExplicitTemplateSpecialization()))));
> + Finder->addMatcher(
> + cxxRecordDecl(
> + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))),
> + unless(isImplicit()), unless(hasAncestor(cxxRecordDecl())),
> + unless(isInstantiated()), unless(IsInSpecialization),
> + unless(classTemplateSpecializationDecl()))
> + .bind("record_decl"),
> + this);
> +
> + // Match all friend declarations. Classes used in friend declarations
> are not
> + // marked as referenced in AST. We need to record all record classes
> used in
> + // friend declarations.
> + Finder->addMatcher(friendDecl().bind("friend_decl"), this);
> +}
> +
> +void ForwardDeclarationNamespaceCheck::check(
> + const MatchFinder::MatchResult &Result) {
> + if (const auto *RecordDecl =
> + Result.Nodes.getNodeAs<CXXRecordDecl>("record_decl")) {
> + StringRef DeclName = RecordDecl->getName();
> + if (RecordDecl->isThisDeclarationADefinition()) {
> + DeclNameToDefinitions[DeclName].push_back(RecordDecl);
> + } else {
> + // If a declaration has no definition, the definition could be in
> another
> + // namespace (a wrong namespace).
> + // NOTE: even a declaration does have definition, we still need it
> to
> + // compare with other declarations.
> + DeclNameToDeclarations[DeclName].push_back(RecordDecl);
> + }
> + } else {
> + const auto *Decl = Result.Nodes.getNodeAs<FriendDecl>("friend_decl");
> + assert(Decl && "Decl is neither record_decl nor friend decl!");
> +
> + // Classes used in friend delarations are not marked referenced in
> AST,
> + // so we need to check classes used in friend declarations manually to
> + // reduce the rate of false positive.
> + // For example, in
> + // \code
> + // struct A;
> + // struct B { friend A; };
> + // \endcode
> + // `A` will not be marked as "referenced" in the AST.
> + if (const TypeSourceInfo *Tsi = Decl->getFriendType()) {
> + QualType Desugared =
> Tsi->getType().getDesugaredType(*Result.Context);
> + FriendTypes.insert(Desugared.getTypePtr());
> + }
> + }
> +}
> +
> +static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1,
> + const CXXRecordDecl
> *Decl2) {
> + const DeclContext *ParentDecl1 = Decl1->getLexicalParent();
> + const DeclContext *ParentDecl2 = Decl2->getLexicalParent();
> +
> + // Since we only matched declarations whose parent is Namespace or
> + // TranslationUnit declaration, the parent should be either a
> translation unit
> + // or namespace.
> + if (ParentDecl1->getDeclKind() == Decl::TranslationUnit ||
> + ParentDecl2->getDeclKind() == Decl::TranslationUnit) {
> + return ParentDecl1 == ParentDecl2;
> + }
> + assert(ParentDecl1->getDeclKind() == Decl::Namespace &&
> + "ParentDecl1 declaration must be a namespace");
> + assert(ParentDecl2->getDeclKind() == Decl::Namespace &&
> + "ParentDecl2 declaration must be a namespace");
> + auto *Ns1 = NamespaceDecl::castFromDeclContext(ParentDecl1);
> + auto *Ns2 = NamespaceDecl::castFromDeclContext(ParentDecl2);
> + return Ns1->getOriginalNamespace() == Ns2->getOriginalNamespace();
> +}
> +
> +static std::string getNameOfNamespace(const CXXRecordDecl *Decl) {
> + const auto *ParentDecl = Decl->getLexicalParent();
> + if (ParentDecl->getDeclKind() == Decl::TranslationUnit) {
> + return "(global)";
> + }
> + const auto *NsDecl = cast<NamespaceDecl>(ParentDecl);
> + std::string Ns;
> + llvm::raw_string_ostream OStream(Ns);
> + NsDecl->printQualifiedName(OStream);
> + OStream.flush();
> + return Ns.empty() ? "(global)" : Ns;
> +}
> +
> +void ForwardDeclarationNamespaceCheck::onEndOfTranslationUnit() {
> + // Iterate each group of declarations by name.
> + for (const auto &KeyValuePair : DeclNameToDeclarations) {
> + const auto &Declarations = KeyValuePair.second;
> + // If more than 1 declaration exists, we check if all are in the same
> + // namespace.
> + for (const auto *CurDecl : Declarations) {
> + if (CurDecl->hasDefinition() || CurDecl->isReferenced()) {
> + continue; // Skip forward declarations that are used/referenced.
> + }
> + if (FriendTypes.count(CurDecl->getTypeForDecl()) != 0) {
> + continue; // Skip forward declarations referenced as friend.
> + }
> + if (CurDecl->getLocation().isMacroID() ||
> + CurDecl->getLocation().isInvalid()) {
> + continue;
> + }
> + // Compare with all other declarations with the same name.
> + for (const auto *Decl : Declarations) {
> + if (Decl == CurDecl) {
> + continue; // Don't compare with self.
> + }
> + if (!CurDecl->hasDefinition() &&
> + !haveSameNamespaceOrTranslationUnit(CurDecl, Decl)) {
> + diag(CurDecl->getLocation(),
> + "declaration '%0' is never referenced, but a declaration
> with "
> + "the same name found in another namespace '%1'")
> + << CurDecl->getName() << getNameOfNamespace(Decl);
> + diag(Decl->getLocation(), "a declaration of '%0' is found here",
> + DiagnosticIDs::Note)
> + << Decl->getName();
> + break; // FIXME: We only generate one warning for each
> declaration.
> + }
> + }
> + // Check if a definition in another namespace exists.
> + const auto DeclName = CurDecl->getName();
> + if (DeclNameToDefinitions.find(DeclName) ==
> DeclNameToDefinitions.end()) {
> + continue; // No definition in this translation unit, we can skip
> it.
> + }
> + // Make a warning for each definition with the same name (in other
> + // namespaces).
> + const auto &Definitions = DeclNameToDefinitions[DeclName];
> + for (const auto *Def : Definitions) {
> + diag(CurDecl->getLocation(),
> + "no definition found for '%0', but a definition with "
> + "the same name '%1' found in another namespace '%2'")
> + << CurDecl->getName() << Def->getName() <<
> getNameOfNamespace(Def);
> + diag(Def->getLocation(), "a definition of '%0' is found here",
> + DiagnosticIDs::Note)
> + << Def->getName();
> + }
> + }
> + }
> +}
> +
> +} // namespace misc
> +} // namespace tidy
> +} // namespace clang
>
> Added:
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h
> URL:
> http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h?rev=261737&view=auto
>
> ==============================================================================
> ---
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h
> (added)
> +++
> clang-tools-extra/trunk/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h
> Wed Feb 24 07:35:32 2016
> @@ -0,0 +1,59 @@
> +//===--- ForwardDeclarationNamespaceCheck.h - clang-tidy --------*- C++
> -*-===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
>
> +//===----------------------------------------------------------------------===//
> +
> +#ifndef
> LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H
> +#define
> LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H
> +
> +#include <set>
> +#include <vector>
> +#include "llvm/ADT/SmallPtrSet.h"
> +#include "../ClangTidy.h"
> +
> +namespace clang {
> +namespace tidy {
> +namespace misc {
> +
> +/// Checks if an unused forward declaration is in a wrong namespace.
> +///
> +/// The check inspects all unused forward declarations and checks if
> there is
> +/// any declaration/definition with the same name, which could indicate
> +/// that the forward declaration is potentially in a wrong namespace.
> +///
> +/// \code
> +/// namespace na { struct A; }
> +/// namespace nb { struct A {} };
> +/// nb::A a;
> +/// // warning : no definition found for 'A', but a definition with the
> same
> +/// name 'A' found in another namespace 'nb::'
> +/// \endcode
> +///
> +/// This check can only generate warnings, but it can't suggest fixes at
> this
> +/// point.
> +///
> +/// For the user-facing documentation see:
> +///
> http://clang.llvm.org/extra/clang-tidy/checks/misc-forward-declaration-namespace.html
> +class ForwardDeclarationNamespaceCheck : public ClangTidyCheck {
> +public:
> + ForwardDeclarationNamespaceCheck(StringRef Name, ClangTidyContext
> *Context)
> + : ClangTidyCheck(Name, Context) {}
> + void registerMatchers(ast_matchers::MatchFinder *Finder) override;
> + void check(const ast_matchers::MatchFinder::MatchResult &Result)
> override;
> + void onEndOfTranslationUnit() override;
> +
> +private:
> + llvm::StringMap<std::vector<const CXXRecordDecl *>>
> DeclNameToDefinitions;
> + llvm::StringMap<std::vector<const CXXRecordDecl *>>
> DeclNameToDeclarations;
> + llvm::SmallPtrSet<const Type *, 16> FriendTypes;
> +};
> +
> +} // namespace misc
> +} // namespace tidy
> +} // namespace clang
> +
> +#endif //
> LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H
>
> Modified: clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp
> URL:
> http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp?rev=261737&r1=261736&r2=261737&view=diff
>
> ==============================================================================
> --- clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp (original)
> +++ clang-tools-extra/trunk/clang-tidy/misc/MiscTidyModule.cpp Wed Feb 24
> 07:35:32 2016
> @@ -15,6 +15,7 @@
> #include "AssignOperatorSignatureCheck.h"
> #include "BoolPointerImplicitConversionCheck.h"
> #include "DefinitionsInHeadersCheck.h"
> +#include "ForwardDeclarationNamespaceCheck.h"
> #include "InaccurateEraseCheck.h"
> #include "IncorrectRoundings.h"
> #include "InefficientAlgorithmCheck.h"
> @@ -55,6 +56,8 @@ public:
> "misc-bool-pointer-implicit-conversion");
> CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
> "misc-definitions-in-headers");
> + CheckFactories.registerCheck<ForwardDeclarationNamespaceCheck>(
> + "misc-forward-declaration-namespace");
> CheckFactories.registerCheck<InaccurateEraseCheck>(
> "misc-inaccurate-erase");
> CheckFactories.registerCheck<IncorrectRoundings>(
>
> Modified: clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst
> URL:
> http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst?rev=261737&r1=261736&r2=261737&view=diff
>
> ==============================================================================
> --- clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst (original)
> +++ clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst Wed Feb 24
> 07:35:32 2016
> @@ -51,6 +51,7 @@ Clang-Tidy Checks
> misc-assign-operator-signature
> misc-bool-pointer-implicit-conversion
> misc-definitions-in-headers
> + misc-forward-declaration-namespace
> misc-inaccurate-erase
> misc-incorrect-roundings
> misc-inefficient-algorithm
>
> Added:
> clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst
> URL:
> http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst?rev=261737&view=auto
>
> ==============================================================================
> ---
> clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst
> (added)
> +++
> clang-tools-extra/trunk/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst
> Wed Feb 24 07:35:32 2016
> @@ -0,0 +1,19 @@
> +.. title:: clang-tidy - misc-forward-declaration-namespace
> +
> +misc-forward-declaration-namespace
> +==================================
> +
> +Checks if an unused forward declaration is in a wrong namespace.
> +
> +The check inspects all unused forward declarations and checks if there is
> any
> +declaration/definition with the same name existing, which could indicate
> that
> +the forward declaration is in a potentially wrong namespace.
> +
> +.. code:: c++
> + namespace na { struct A; }
> + namespace nb { struct A {}; }
> + nb::A a;
> + // warning : no definition found for 'A', but a definition with the
> same name
> + // 'A' found in another namespace 'nb::'
> +
> +This check can only generate warnings, but it can't suggest a fix at this
> point.
>
> Added:
> clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp
> URL:
> http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp?rev=261737&view=auto
>
> ==============================================================================
> ---
> clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp
> (added)
> +++
> clang-tools-extra/trunk/test/clang-tidy/misc-forward-declaration-namespace.cpp
> Wed Feb 24 07:35:32 2016
> @@ -0,0 +1,163 @@
> +// RUN: %check_clang_tidy %s misc-forward-declaration-namespace %t
> +
> +namespace {
> +// This is a declaration in a wrong namespace.
> +class T_A;
> +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_A' is never
> referenced, but a declaration with the same name found in another namespace
> 'na' [misc-forward-declaration-namespace]
> +// CHECK-MESSAGES: note: a declaration of 'T_A' is found here
> +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for
> 'T_A', but a definition with the same name 'T_A' found in another namespace
> '(global)' [misc-forward-declaration-namespace]
> +// CHECK-MESSAGES: note: a definition of 'T_A' is found here
> +}
> +
> +namespace na {
> +// This is a declaration in a wrong namespace.
> +class T_A;
> +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_A' is never
> referenced, but a declaration with the same name found in another namespace
> '(anonymous)'
> +// CHECK-MESSAGES: note: a declaration of 'T_A' is found here
> +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for
> 'T_A', but a definition with the same name 'T_A' found in another namespace
> '(global)'
> +// CHECK-MESSAGES: note: a definition of 'T_A' is found here
> +}
> +
> +class T_A;
> +
> +class T_A {
> + int x;
> +};
> +
> +class NESTED;
> +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: no definition found for
> 'NESTED', but a definition with the same name 'NESTED' found in another
> namespace '(anonymous namespace)::nq::(anonymous)'
> +// CHECK-MESSAGES: note: a definition of 'NESTED' is found here
> +
> +namespace {
> +namespace nq {
> +namespace {
> +class NESTED {};
> +}
> +}
> +}
> +
> +namespace na {
> +class T_B;
> +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_B' is never
> referenced, but a declaration with the same name found in another namespace
> 'nb'
> +// CHECK-MESSAGES: note: a declaration of 'T_B' is found here
> +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for
> 'T_B', but a definition with the same name 'T_B' found in another namespace
> 'nb'
> +// CHECK-MESSAGES: note: a definition of 'T_B' is found here
> +}
> +
> +namespace nb {
> +class T_B;
> +}
> +
> +namespace nb {
> +class T_B {
> + int x;
> +};
> +}
> +
> +namespace na {
> +class T_B;
> +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'T_B' is never
> referenced, but a declaration with the same name found in another namespace
> 'nb'
> +// CHECK-MESSAGES: note: a declaration of 'T_B' is found here
> +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: no definition found for
> 'T_B', but a definition with the same name 'T_B' found in another namespace
> 'nb'
> +// CHECK-MESSAGES: note: a definition of 'T_B' is found here
> +}
> +
> +// A simple forward declaration. Although it is never used, but no
> declaration
> +// with the same name is found in other namespace.
> +class OUTSIDER;
> +
> +namespace na {
> +// This class is referenced declaration, we don't generate warning.
> +class OUTSIDER_1;
> +}
> +
> +void f(na::OUTSIDER_1);
> +
> +namespace nc {
> +// This class is referenced as friend in OOP.
> +class OUTSIDER_1;
> +
> +class OOP {
> + friend struct OUTSIDER_1;
> +};
> +}
> +
> +namespace nd {
> +class OUTSIDER_1;
> +void f(OUTSIDER_1 *);
> +}
> +
> +namespace nb {
> +class OUTSIDER_1;
> +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'OUTSIDER_1' is
> never referenced, but a declaration with the same name found in another
> namespace 'na'
> +// CHECK-MESSAGES: note: a declaration of 'OUTSIDER_1' is found here
> +}
> +
> +
> +namespace na {
> +template<typename T>
> +class T_C;
> +}
> +
> +namespace nb {
> +// FIXME: this is an error, but we don't consider template class
> declaration
> +// now.
> +template<typename T>
> +class T_C;
> +}
> +
> +namespace na {
> +template<typename T>
> +class T_C {
> + int x;
> +};
> +}
> +
> +namespace na {
> +
> +template <typename T>
> +class T_TEMP {
> + template <typename _Tp1>
> + struct rebind { typedef T_TEMP<_Tp1> other; };
> +};
> +
> +// We ignore class template specialization.
> +template class T_TEMP<char>;
> +}
> +
> +namespace nb {
> +
> +template <typename T>
> +class T_TEMP_1 {
> + template <typename _Tp1>
> + struct rebind { typedef T_TEMP_1<_Tp1> other; };
> +};
> +
> +// We ignore class template specialization.
> +extern template class T_TEMP_1<char>;
> +}
> +
> +namespace nd {
> +class D;
> +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: declaration 'D' is never
> referenced, but a declaration with the same name found in another namespace
> 'nd::ne'
> +// CHECK-MESSAGES: note: a declaration of 'D' is found here
> +}
> +
> +namespace nd {
> +namespace ne {
> +class D;
> +}
> +}
> +
> +int f(nd::ne::D &d);
> +
> +
> +// This should be ignored by the check.
> +template <typename... Args>
> +class Observer {
> + class Impl;
> +};
> +
> +template <typename... Args>
> +class Observer<Args...>::Impl {
> +};
>
>
> _______________________________________________
> cfe-commits mailing list
> cfe-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20160224/e632f854/attachment-0001.html>
More information about the cfe-commits
mailing list