EricWF updated this revision to Diff 67790.
EricWF marked an inline comment as done.
EricWF added a comment.

- Add documentation for `__has_constant_initializer` and expression traits in 
general.
- Make expression traits detectable using `__has_extension`.


https://reviews.llvm.org/D23385

Files:
  docs/LanguageExtensions.rst
  include/clang/AST/Expr.h
  include/clang/Basic/DiagnosticSemaKinds.td
  include/clang/Basic/ExpressionTraits.h
  include/clang/Basic/TokenKinds.def
  lib/AST/Expr.cpp
  lib/AST/StmtPrinter.cpp
  lib/Lex/PPMacroExpansion.cpp
  lib/Parse/ParseDeclCXX.cpp
  lib/Parse/ParseExpr.cpp
  lib/Parse/ParseExprCXX.cpp
  lib/Sema/SemaExprCXX.cpp
  test/Lexer/has_extension_cxx.cpp
  test/SemaCXX/expression-traits.cpp

Index: test/SemaCXX/expression-traits.cpp
===================================================================
--- test/SemaCXX/expression-traits.cpp
+++ test/SemaCXX/expression-traits.cpp
@@ -1,5 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions %s
-
+// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -std=c++14 %s
 //
 // Tests for "expression traits" intrinsics such as __is_lvalue_expr.
 //
@@ -617,3 +617,275 @@
 {
     check_temp_param_6<3,AnInt>();
 }
+
+//===========================================================================//
+// __has_constant_initializer
+//===========================================================================//
+struct PODType {
+    int value;
+    int value2;
+};
+#if __cplusplus >= 201103L
+struct LitType {
+    constexpr LitType() : value(0) {}
+    constexpr LitType(int x) : value(x) {}
+    LitType(void*) : value(-1) {}
+    int value;
+};
+#endif
+
+struct NonLit {
+#if __cplusplus >= 201402L
+    constexpr NonLit() : value(0) {}
+    constexpr NonLit(int x ) : value(x) {}
+#else
+    NonLit() : value(0) {}
+    NonLit(int x) : value(x) {}
+#endif
+    NonLit(void*) : value(-1) {}
+    ~NonLit() {}
+    int value;
+};
+
+struct StoresNonLit {
+#if __cplusplus >= 201402L
+    constexpr StoresNonLit() : obj() {}
+    constexpr StoresNonLit(int x) : obj(x) {}
+#else
+    StoresNonLit() : obj() {}
+    StoresNonLit(int x) : obj(x) {}
+#endif
+    StoresNonLit(void* p) : obj(p) {}
+    NonLit obj;
+};
+
+const bool NonLitHasConstInit =
+#if __cplusplus >= 201402L
+    true;
+#else
+    false;
+#endif
+
+// Test diagnostics when the argument does not refer to a named identifier
+void check_is_constant_init_bogus()
+{
+    (void)__has_constant_initializer(42); // expected-error {{does not reference a named variable}}
+    (void)__has_constant_initializer(ReturnInt()); // expected-error {{does not reference a named variable}}
+    (void)__has_constant_initializer(42, 43); // expected-error {{does not reference a named variable}}
+}
+
+// [basic.start.static]p2.1
+// if each full-expression (including implicit conversions) that appears in
+// the initializer of a reference with static or thread storage duration is
+// a constant expression (5.20) and the reference is bound to a glvalue
+// designating an object with static storage duration, to a temporary object
+// (see 12.2) or subobject thereof, or to a function;
+
+// Test binding to a static glvalue
+const int glvalue_int = 42;
+const int glvalue_int2 = ReturnInt();
+const int& glvalue_ref = glvalue_int;
+const int& glvalue_ref2 = glvalue_int2;
+static_assert(__has_constant_initializer(glvalue_ref), "");
+static_assert(__has_constant_initializer(glvalue_ref2), "");
+
+__thread const int& glvalue_ref_tl = glvalue_int;
+static_assert(__has_constant_initializer(glvalue_ref_tl), "");
+
+void test_basic_start_static_2_1() {
+    const int non_global = 42;
+    const int& non_global_ref = non_global;
+    static_assert(!__has_constant_initializer(non_global), "automatic variables never have const init");
+    static_assert(!__has_constant_initializer(non_global_ref), "automatic variables never have const init");
+
+    static const int& local_init = non_global;
+    static_assert(!__has_constant_initializer(local_init), "init must be static glvalue");
+    static const int& global_init = glvalue_int;
+    static_assert(__has_constant_initializer(global_init), "");
+    static const int& temp_init = 42;
+    static_assert(__has_constant_initializer(temp_init), "");
+#if 0
+    /// FIXME: Why is this failing?
+    __thread const int& tl_init = 42;
+    static_assert(__has_constant_initializer(tl_init), "");
+#endif
+}
+
+const int& temp_ref = 42;
+const int& temp_ref2 = ReturnInt();
+static_assert(__has_constant_initializer(temp_ref), "");
+static_assert(!__has_constant_initializer(temp_ref2), "");
+
+const NonLit& nl_temp_ref = 42;
+static_assert(!__has_constant_initializer(nl_temp_ref), "");
+
+#if __cplusplus >= 201103L
+const LitType& lit_temp_ref = 42;
+static_assert(__has_constant_initializer(lit_temp_ref), "");
+
+const int& subobj_ref = LitType{}.value;
+static_assert(__has_constant_initializer(subobj_ref), "");
+#endif
+
+const int& nl_subobj_ref = NonLit().value;
+static_assert(!__has_constant_initializer(nl_subobj_ref), "");
+
+struct TT1 {
+  static const int& no_init;
+  static const int& glvalue_init;
+  static const int& temp_init;
+  static const int& subobj_init;
+#if __cplusplus >= 201103L
+  static thread_local const int& tl_glvalue_init;
+  static thread_local const int& tl_temp_init;
+#endif
+};
+const int& TT1::glvalue_init = glvalue_int;
+const int& TT1::temp_init = 42;
+const int& TT1::subobj_init = PODType().value;
+
+static_assert(!__has_constant_initializer(TT1::no_init), "");
+static_assert(__has_constant_initializer(TT1::glvalue_init), "");
+static_assert(__has_constant_initializer(TT1::temp_init), "");
+static_assert(__has_constant_initializer(TT1::subobj_init), "");
+#if __cplusplus >= 201103L
+thread_local const int& TT1::tl_glvalue_init = glvalue_int;
+thread_local const int& TT1::tl_temp_init = 42;
+static_assert(__has_constant_initializer(TT1::tl_glvalue_init), "");
+static_assert(!__has_constant_initializer(TT1::tl_temp_init), "");
+#endif
+
+// [basic.start.static]p2.2
+// if an object with static or thread storage duration is initialized by a
+// constructor call, and if the initialization full-expression is a constant
+// initializer for the object;
+
+void test_basic_start_static_2_2()
+{
+    static PODType pod;
+    static_assert(__has_constant_initializer(pod), "");
+
+#if __cplusplus >= 201103L
+    constexpr LitType l;
+    static_assert(!__has_constant_initializer(l), "non-static objects don't have const init");
+
+    static LitType static_lit = l;
+    static_assert(__has_constant_initializer(static_lit), "");
+
+    static LitType static_lit2 = (void*)0;
+    static_assert(!__has_constant_initializer(static_lit2), "constructor not constexpr");
+
+    static LitType static_lit3 = ReturnInt();
+    static_assert(!__has_constant_initializer(static_lit3), "initializer not constexpr");
+
+    thread_local LitType tls = 42;
+    static_assert(__has_constant_initializer(tls), "");
+#endif
+}
+
+struct TT2 {
+  static PODType pod_noinit;
+  static PODType pod_copy_init;
+#if __cplusplus >= 201103L
+  static constexpr LitType lit = {};
+  static const NonLit non_lit;
+  static const NonLit non_lit_copy_init;
+#endif
+};
+PODType TT2::pod_noinit;
+PODType TT2::pod_copy_init(TT2::pod_noinit);
+static_assert(__has_constant_initializer(TT2::pod_noinit), "");
+static_assert(!__has_constant_initializer(TT2::pod_copy_init), "");
+#if __cplusplus >= 201103L
+const NonLit TT2::non_lit(42);
+const NonLit TT2::non_lit_copy_init = 42;
+static_assert(__has_constant_initializer(TT2::lit), "");
+static_assert(__has_constant_initializer(TT2::non_lit) == NonLitHasConstInit, "");
+static_assert(!__has_constant_initializer(TT2::non_lit_copy_init), "");
+#endif
+
+#if __cplusplus >= 201103L
+LitType lit_ctor;
+LitType lit_ctor2{};
+LitType lit_ctor3 = {};
+__thread LitType lit_ctor_tl = {};
+static_assert(__has_constant_initializer(lit_ctor), "");
+static_assert(__has_constant_initializer(lit_ctor2), "");
+static_assert(__has_constant_initializer(lit_ctor3), "");
+static_assert(__has_constant_initializer(lit_ctor_tl), "");
+
+NonLit nl_ctor;
+NonLit nl_ctor2{};
+NonLit nl_ctor3 = {};
+thread_local NonLit nl_ctor_tl = {};
+static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor), "");
+static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor2), "");
+static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor3), "");
+static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor_tl), "");
+
+StoresNonLit snl;
+static_assert(NonLitHasConstInit == __has_constant_initializer(snl), "");
+
+// Non-literal types cannot appear in the initializer of a non-literal type.
+int nl_in_init = NonLit{42}.value;
+static_assert(!__has_constant_initializer(nl_in_init), "");
+
+int lit_in_init = LitType{42}.value;
+static_assert(__has_constant_initializer(lit_in_init), "");
+#endif
+
+// [basic.start.static]p2.3
+// if an object with static or thread storage duration is not initialized by a
+// constructor call and if either the object is value-initialized or every
+// full-expression that appears in its initializer is a constant expression.
+void test_basic_start_static_2_3()
+{
+    const int local = 42;
+    static_assert(!__has_constant_initializer(local), "automatic variable does not have const init");
+
+    static int static_local = 42;
+    static_assert(__has_constant_initializer(static_local), "");
+
+    static int static_local2;
+    static_assert(!__has_constant_initializer(static_local2), "no init");
+
+#if __cplusplus >= 201103L
+    thread_local int tl_local = 42;
+    static_assert(__has_constant_initializer(tl_local), "");
+#endif
+}
+
+int no_init;
+static_assert(!__has_constant_initializer(no_init), "");
+
+int arg_init = 42;
+static_assert(__has_constant_initializer(arg_init), "");
+
+PODType pod_init = {};
+static_assert(__has_constant_initializer(pod_init), "");
+
+PODType pod_missing_init = {42 /* should have second arg */};
+static_assert(__has_constant_initializer(pod_missing_init), "");
+
+PODType pod_full_init = {1, 2};
+static_assert(__has_constant_initializer(pod_full_init), "");
+
+PODType pod_non_constexpr_init = {1, ReturnInt()};
+static_assert(!__has_constant_initializer(pod_non_constexpr_init), "");
+
+#if __cplusplus >= 201103L
+int val_init{};
+static_assert(__has_constant_initializer(val_init), "");
+
+int brace_init = {};
+static_assert(__has_constant_initializer(brace_init), "");
+#endif
+
+__thread int tl_init = 0;
+static_assert(__has_constant_initializer(tl_init), "");
+
+void foo1(int p = 42) {
+  // ParmVarDecl's with an initializer do not have static or thread local
+  // storage duration.
+  static_assert(!__has_constant_initializer(p), "");
+}
Index: test/Lexer/has_extension_cxx.cpp
===================================================================
--- test/Lexer/has_extension_cxx.cpp
+++ test/Lexer/has_extension_cxx.cpp
@@ -66,3 +66,8 @@
 #if __has_extension(cxx_init_captures)
 int has_init_captures();
 #endif
+
+// CHECK: has_constant_initializer
+#if __has_extension(has_constant_initializer)
+int has_constant_initializer();
+#endif
Index: lib/Sema/SemaExprCXX.cpp
===================================================================
--- lib/Sema/SemaExprCXX.cpp
+++ lib/Sema/SemaExprCXX.cpp
@@ -4749,10 +4749,39 @@
   return Result;
 }
 
-static bool EvaluateExpressionTrait(ExpressionTrait ET, Expr *E) {
+static bool EvaluateExpressionTrait(Sema &Self, ExpressionTrait ET,
+                                    SourceLocation KWLoc, Expr *E,
+                                    SourceLocation RParen) {
   switch (ET) {
   case ET_IsLValueExpr: return E->isLValue();
   case ET_IsRValueExpr: return E->isRValue();
+  case ET_HasConstantInitializer: {
+    // Check for 'constant initialization' according to [basic.start.static].
+    DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreImpCasts());
+    if (!DRE) {
+      // It is a usage error to specify an expression that does not reference
+      // a named variable.
+      Self.Diag(KWLoc, diag::err_has_constant_init_expression_not_named_var)
+        << E->getSourceRange();
+      return false;
+    }
+    if (VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+      // Thread local objects specified as TSL_Static have a constant
+      // initializer. TLS_Dynamic objects still need to be checked below.
+      if (VD->getTLSKind() == VarDecl::TLS_Static)
+        return true;
+      // Check the initializer of objects with static or thread-local storage
+      // duration. AObjects with automatic or dynamic lifetime never have
+      // a 'constant initializer'.
+      if ((VD->hasGlobalStorage() ||
+          VD->getTLSKind() != VarDecl::TLS_None) && VD->hasInit()) {
+        QualType baseType = Self.Context.getBaseElementType(VD->getType());
+        return VD->getInit()->isConstantInitializer(Self.Context,
+            baseType->isReferenceType(), nullptr, /*AllowNonLiteral=*/true);
+      }
+    }
+    return false;
+  }
   }
   llvm_unreachable("Expression trait not covered by switch");
 }
@@ -4769,7 +4798,7 @@
     return BuildExpressionTrait(ET, KWLoc, PE.get(), RParen);
   }
 
-  bool Value = EvaluateExpressionTrait(ET, Queried);
+  bool Value = EvaluateExpressionTrait(*this, ET, KWLoc, Queried, RParen);
 
   return new (Context)
       ExpressionTraitExpr(KWLoc, ET, Queried, Value, RParen, Context.BoolTy);
Index: lib/Parse/ParseExprCXX.cpp
===================================================================
--- lib/Parse/ParseExprCXX.cpp
+++ lib/Parse/ParseExprCXX.cpp
@@ -2949,6 +2949,7 @@
 static ExpressionTrait ExpressionTraitFromTokKind(tok::TokenKind kind) {
   switch(kind) {
   default: llvm_unreachable("Not a known unary expression trait.");
+  case tok::kw___has_constant_initializer:   return ET_HasConstantInitializer;
   case tok::kw___is_lvalue_expr:             return ET_IsLValueExpr;
   case tok::kw___is_rvalue_expr:             return ET_IsRValueExpr;
   }
@@ -3083,6 +3084,7 @@
   if (T.expectAndConsume())
     return ExprError();
 
+  EnterExpressionEvaluationContext Unevaluated(Actions, Sema::Unevaluated);
   ExprResult Expr = ParseExpression();
 
   T.consumeClose();
Index: lib/Parse/ParseExpr.cpp
===================================================================
--- lib/Parse/ParseExpr.cpp
+++ lib/Parse/ParseExpr.cpp
@@ -677,11 +677,14 @@
 ///                   '__trivially_copyable'
 ///
 ///       binary-type-trait:
-/// [GNU]             '__is_base_of'       
+/// [GNU]             '__is_base_of'
 /// [MS]              '__is_convertible_to'
 ///                   '__is_convertible'
 ///                   '__is_same'
 ///
+/// [Clang] expression-trait:
+///                   '__has_constant_initializer'
+///
 /// [Embarcadero] array-type-trait:
 ///                   '__array_rank'
 ///                   '__array_extent'
@@ -797,6 +800,7 @@
           RevertibleTypeTraits[PP.getIdentifierInfo(#Name)] \
             = RTT_JOIN(tok::kw_,Name)
 
+          REVERTIBLE_TYPE_TRAIT(__has_constant_initializer);
           REVERTIBLE_TYPE_TRAIT(__is_abstract);
           REVERTIBLE_TYPE_TRAIT(__is_arithmetic);
           REVERTIBLE_TYPE_TRAIT(__is_array);
@@ -1321,10 +1325,11 @@
   case tok::kw___array_extent:
     return ParseArrayTypeTrait();
 
+  case tok::kw___has_constant_initializer:
   case tok::kw___is_lvalue_expr:
   case tok::kw___is_rvalue_expr:
     return ParseExpressionTrait();
-      
+
   case tok::at: {
     SourceLocation AtLoc = ConsumeToken();
     return ParseObjCAtExpression(AtLoc);
Index: lib/Parse/ParseDeclCXX.cpp
===================================================================
--- lib/Parse/ParseDeclCXX.cpp
+++ lib/Parse/ParseDeclCXX.cpp
@@ -1273,7 +1273,8 @@
       Tok.isNot(tok::identifier) &&
       !Tok.isAnnotation() &&
       Tok.getIdentifierInfo() &&
-      Tok.isOneOf(tok::kw___is_abstract,
+      Tok.isOneOf(tok::kw___has_constant_initializer,
+                  tok::kw___is_abstract,
                   tok::kw___is_arithmetic,
                   tok::kw___is_array,
                   tok::kw___is_assignable,
@@ -3667,8 +3668,8 @@
     return true;
   case AttributeList::AT_WarnUnusedResult:
     return !ScopeName && AttrName->getName().equals("nodiscard");
-  case AttributeList::AT_Unused:
-    return !ScopeName && AttrName->getName().equals("maybe_unused");
+  case AttributeList::AT_Unused:
+    return !ScopeName && AttrName->getName().equals("maybe_unused");
   default:
     return false;
   }
Index: lib/Lex/PPMacroExpansion.cpp
===================================================================
--- lib/Lex/PPMacroExpansion.cpp
+++ lib/Lex/PPMacroExpansion.cpp
@@ -1267,6 +1267,10 @@
            .Case("cxx_binary_literals", true)
            .Case("cxx_init_captures", LangOpts.CPlusPlus11)
            .Case("cxx_variable_templates", LangOpts.CPlusPlus)
+           // Expression traits
+           .Case("has_constant_initializer", LangOpts.CPlusPlus)
+           .Case("is_lvalue_expr", LangOpts.CPlusPlus)
+           .Case("is_rvalue_expr", LangOpts.CPlusPlus)
            .Default(false);
 }
 
Index: lib/AST/StmtPrinter.cpp
===================================================================
--- lib/AST/StmtPrinter.cpp
+++ lib/AST/StmtPrinter.cpp
@@ -2321,8 +2321,9 @@
 
 static const char *getExpressionTraitName(ExpressionTrait ET) {
   switch (ET) {
-  case ET_IsLValueExpr:      return "__is_lvalue_expr";
-  case ET_IsRValueExpr:      return "__is_rvalue_expr";
+  case ET_IsLValueExpr:           return "__is_lvalue_expr";
+  case ET_IsRValueExpr:           return "__is_rvalue_expr";
+  case ET_HasConstantInitializer:  return "__has_constant_initializer";
   }
   llvm_unreachable("Expression type trait not covered by switch");
 }
Index: lib/AST/Expr.cpp
===================================================================
--- lib/AST/Expr.cpp
+++ lib/AST/Expr.cpp
@@ -2612,7 +2612,8 @@
 }
 
 bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef,
-                                 const Expr **Culprit) const {
+                                 const Expr **Culprit,
+                                 bool AllowNonLiteral) const {
   // This function is attempting whether an expression is an initializer
   // which can be evaluated at compile-time. It very closely parallels
   // ConstExprEmitter in CGExprConstant.cpp; if they don't match, it
@@ -2649,7 +2650,15 @@
       assert(CE->getNumArgs() == 1 && "trivial ctor with > 1 argument");
       return CE->getArg(0)->isConstantInitializer(Ctx, false, Culprit);
     }
-
+    if (CE->getConstructor()->isConstexpr() &&
+        (CE->getConstructor()->getParent()->hasTrivialDestructor() ||
+            AllowNonLiteral)) {
+        for (auto *Arg : CE->arguments()) {
+          if (!Arg->isConstantInitializer(Ctx, false, Culprit))
+            return false;
+        }
+        return true;
+    }
     break;
   }
   case CompoundLiteralExprClass: {
Index: include/clang/Basic/TokenKinds.def
===================================================================
--- include/clang/Basic/TokenKinds.def
+++ include/clang/Basic/TokenKinds.def
@@ -454,6 +454,9 @@
 TYPE_TRAIT_2(__is_trivially_assignable, IsTriviallyAssignable, KEYCXX)
 KEYWORD(__underlying_type           , KEYCXX)
 
+// Clang-only C++ Expression Traits
+KEYWORD(__has_constant_initializer  , KEYCXX)
+
 // Embarcadero Expression Traits
 KEYWORD(__is_lvalue_expr            , KEYCXX)
 KEYWORD(__is_rvalue_expr            , KEYCXX)
Index: include/clang/Basic/ExpressionTraits.h
===================================================================
--- include/clang/Basic/ExpressionTraits.h
+++ include/clang/Basic/ExpressionTraits.h
@@ -19,7 +19,8 @@
 
   enum ExpressionTrait {
     ET_IsLValueExpr,
-    ET_IsRValueExpr
+    ET_IsRValueExpr,
+    ET_HasConstantInitializer
   };
 }
 
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -6837,7 +6837,10 @@
 
 def err_incomplete_type_used_in_type_trait_expr : Error<
   "incomplete type %0 used in type trait expression">;
-  
+
+def err_has_constant_init_expression_not_named_var : Error<
+  "expression must be a named variable">;
+
 def err_dimension_expr_not_constant_integer : Error<
   "dimension expression does not evaluate to a constant unsigned int">;
 
Index: include/clang/AST/Expr.h
===================================================================
--- include/clang/AST/Expr.h
+++ include/clang/AST/Expr.h
@@ -526,7 +526,8 @@
   /// If this expression is not constant and Culprit is non-null,
   /// it is used to store the address of first non constant expr.
   bool isConstantInitializer(ASTContext &Ctx, bool ForRef,
-                             const Expr **Culprit = nullptr) const;
+                             const Expr **Culprit = nullptr,
+                             bool AllowNonLiteral = false) const;
 
   /// EvalStatus is a struct with detailed info about an evaluation in progress.
   struct EvalStatus {
Index: docs/LanguageExtensions.rst
===================================================================
--- docs/LanguageExtensions.rst
+++ docs/LanguageExtensions.rst
@@ -943,6 +943,9 @@
 
 More information could be found `here <http://clang.llvm.org/docs/Modules.html>`_.
 
+
+.. _langext-traits_feature_detection:
+
 Checks for Type Trait Primitives
 ================================
 
@@ -1024,6 +1027,41 @@
 * ``__is_nothrow_constructible`` (MSVC 2013, clang)
 * ``__is_assignable`` (MSVC 2015, clang)
 
+
+Expression Traits
+=================
+
+Expression traits are special builtin constant expressions much like type traits
+except that they act on expressions not types. They allow the determination of
+various characteristics of an expression. The supplied expression is treated
+as an unevaluated operand.
+
+Feature detection for expression traits is the same as for type traits.
+See :ref:`Checks for Type Trait Primatives <langext-traits_feature_detection>`
+for information.
+
+The following expression trait primitives are supported by Clang:
+
+* ``__has_constant_initializer(expr)``:
+  Determines whether `expr` names
+  a object that will be initialized during
+  `constant initialization <http://en.cppreference.com/w/cpp/language/constant_initialization>`_
+  according to the rules of [basic.start.static]. If ``expr`` does not name
+  an object, or if the object it names doesn't have static or thread-local
+  storage duration the result is false. This trait can be used determine if it's
+  safe to use a global variable during program startup. For example:
+
+  .. code-block:: c++
+
+    static MyType global_obj;
+    #if __has_extension(has_constant_initializer)
+    static_assert(__has_constant_initializer(global_obj),
+                  "global_obj may be unsafely used before it is initialized");
+    #endif
+
+* ``__is_lvalue_expr(expr)``
+* ``__is_rvalue_expr(expr)``
+
 Blocks
 ======
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to