Hi!
I know P2758R5 didn't make it into C++26, but on IRC Ville said it would be
useful anyway, so here is a quick attempt at implementing it.
Not adding anything on the libstdc++ side, because I don't know where
experimental stuff like that should go, whether it would be in the
implementation namespace etc.
Also, am not sure how exactly the tags should be handled.
Right now in the patch if a tag is provided and matches some known
warning option after the initial -W (say "uninitialized"), then
if it is the warning case, it is printed like
b.C:38:36: warning: constexpr message: foo [-Wuninitialized]
with -Wuninitialized on some terminals underlined with URL to the
docs and it is controllable by -Wuninitialized, but for print/error
the tag is not shown. For unknown tags it prints
a.C:38:28: error: constexpr message with tag baz: cdef
etc. But I'm open to suggestions about the exact wording, whether
the tag should include also the W or even - chatacters at the start,
etc. Also, what to do with unknown tags, whether there will be some new
option (-Wsomething= ?) that allows specification of what user tags
should be warned and what shouldn't. And whether print or error would
be controllable in any way with some command line options.
-fexec-charset= support isn't done yet (it needs some new code introduced
on the reflection branch), but I think static_assert with user message
nor inline asm with constexpr strings work in that case either.
Perhaps the tag should always go last as [whatever] and have whatever
in some specific color...
In any case, the compiler side is just one new builtin,
__builtin_constexpr_diag, whose first argument is 0 for print,
or 1 for warning or 2 for error, second argument is string_view of the tag
(or "" for no tag) and third argument is string_view or u8string_view
with the message. The builtin also handles string literals.
And so that it doesn't spam the user too much, I'm not reporting anything
for mce_unknown, only for mce_true or mce_false.
--- gcc/cp/tree.cc.jj 2025-11-10 12:51:37.425770310 +0100
+++ gcc/cp/tree.cc 2025-11-10 16:12:10.815171065 +0100
@@ -489,6 +489,7 @@ builtin_valid_in_constant_expr_p (const_
case CP_BUILT_IN_IS_CORRESPONDING_MEMBER:
case CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS:
case CP_BUILT_IN_EH_PTR_ADJUST_REF:
+ case CP_BUILT_IN_CONSTEXPR_DIAG:
return true;
default:
break;
--- gcc/cp/constexpr.cc.jj 2025-10-08 17:46:17.913772272 +0200
+++ gcc/cp/constexpr.cc 2025-11-10 19:49:48.927228304 +0100
@@ -2262,6 +2262,257 @@ cxx_eval_cxa_builtin_fn (const constexpr
}
}
+/* Attempt to evaluate T which represents a call to __builtin_constexpr_diag.
+ The arguments should be an integer (0 for inform, 1 for warning, 2 for
+ error) and 2 messages which are either a pointer to a STRING_CST or
+ class with data () and size () member functions like string_view or
+ u8string_view. */
+
+static tree
+cxx_eval_constexpr_diag (const constexpr_ctx *ctx, tree t, bool
*non_constant_p,
+ bool *overflow_p, tree *jump_target)
+{
+ location_t loc = EXPR_LOCATION (t);
+ if (call_expr_nargs (t) != 3)
+ {
+ if (!ctx->quiet)
+ error_at (loc, "wrong number of arguments to %qs call",
+ "__builtin_constexpr_diag");
+ *non_constant_p = true;
+ return t;
+ }
+ tree args[3];
+ for (int i = 0; i < 3; ++i)
+ {
+ tree arg = CALL_EXPR_ARG (t, i);
+ arg = cxx_eval_constant_expression (ctx, arg,
+ (i == 0
+ || POINTER_TYPE_P (TREE_TYPE (arg)))
+ ? vc_prvalue : vc_glvalue,
+ non_constant_p, overflow_p,
+ jump_target);
+ if (*jump_target)
+ return NULL_TREE;
+ if (*non_constant_p)
+ return t;
+ args[i] = arg;
+ }
+ if (TREE_CODE (args[0]) != INTEGER_CST
+ || wi::to_widest (args[0]) < 0
+ || wi::to_widest (args[0]) > 2)
+ {
+ if (!ctx->quiet)
+ error_at (loc, "first %qs call argument should be 0, 1 or 2",
+ "__builtin_constexpr_diag");
+ *non_constant_p = true;
+ return t;
+ }
+ const char *msgs[2] = {};
+ bool to_free[2] = {};
+ int lens[3] = {};
+ int opt_index = OPT_SPECIAL_unknown;
+ diagnostics::kind kind = diagnostics::kind::error;
+ for (int i = 1; i < 3; ++i)
+ {
+ tree arg = args[i];
+ if (POINTER_TYPE_P (TREE_TYPE (arg)))
+ {
+ tree str = arg;
+ STRIP_NOPS (str);
+ if (TREE_CODE (str) == ADDR_EXPR
+ && TREE_CODE (TREE_OPERAND (str, 0)) == STRING_CST)
+ {
+ str = TREE_OPERAND (str, 0);
+ tree eltype = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (str)));
+ if (eltype == char_type_node
+ || (i == 2 && eltype == char8_type_node))
+ arg = str;
+ }
+ }
+ cexpr_str cstr (arg);
+ if (!cstr.type_check (loc, i == 2))
+ {
+ *non_constant_p = true;
+ return t;
+ }
+ if (TREE_CODE (arg) == STRING_CST)
+ cstr.extract (loc, msgs[i - 1], lens[i - 1]);
+ else
+ {
+ /* Can't use cstr.extract because it evaluates the
+ arguments in separate constexpr contexts. */
+ tree msz = cstr.get_sz ();
+ tree mdata = cstr.get_data ();
+ msz = cxx_eval_constant_expression (ctx, msz, vc_prvalue,
+ non_constant_p, overflow_p,
+ jump_target);
+ if (*jump_target)
+ return NULL_TREE;
+ if (*non_constant_p)
+ return t;
+ if (!tree_fits_uhwi_p (msz))
+ {
+ if (!ctx->quiet)
+ error_at (loc, "constexpr string %<size()%> "
+ "must be a constant expression");
+ *non_constant_p = true;
+ goto out;
+ }
+ else if ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (msz)
+ != tree_to_uhwi (msz))
+ {
+ if (!ctx->quiet)
+ error_at (loc, "constexpr string message %<size()%> "
+ "%qE too large", msz);
+ *non_constant_p = true;
+ goto out;
+ }
+ lens[i - 1] = tree_to_uhwi (msz);
+ mdata = cxx_eval_constant_expression (ctx, mdata, vc_prvalue,
+ non_constant_p, overflow_p,
+ jump_target);
+ if (*jump_target)
+ {
+ t = NULL_TREE;
+ goto out;
+ }
+ if (*non_constant_p)
+ goto out;
+ STRIP_NOPS (mdata);
+ if (TREE_CODE (mdata) != ADDR_EXPR)
+ {
+ unhandled:
+ if (!ctx->quiet)
+ error_at (loc, "unhandled return from %<data()%>");
+ *non_constant_p = true;
+ goto out;
+ }
+ tree str = TREE_OPERAND (mdata, 0);
+ unsigned HOST_WIDE_INT off = 0;
+ if (TREE_CODE (str) == ARRAY_REF
+ && tree_fits_uhwi_p (TREE_OPERAND (str, 1)))
+ {
+ off = tree_to_uhwi (TREE_OPERAND (str, 1));
+ str = TREE_OPERAND (str, 0);
+ }
+ str = cxx_eval_constant_expression (ctx, str, vc_prvalue,
+ non_constant_p, overflow_p,
+ jump_target);
+ if (*jump_target)
+ {
+ t = NULL_TREE;
+ goto out;
+ }
+ if (*non_constant_p)
+ goto out;
+ if (TREE_CODE (str) == STRING_CST)
+ {
+ if (TREE_STRING_LENGTH (str) < lens[i - 1]
+ || (unsigned) TREE_STRING_LENGTH (str) < off
+ || (unsigned) TREE_STRING_LENGTH (str) < off + lens[i - 1])
+ goto unhandled;
+ msgs[i - 1] = TREE_STRING_POINTER (str) + off;
+ continue;
+ }
+ if (TREE_CODE (str) != CONSTRUCTOR
+ || TREE_CODE (TREE_TYPE (str)) != ARRAY_TYPE)
+ goto unhandled;
+ char *buf;
+ if (lens[i - 1] < 64)
+ buf = XALLOCAVEC (char, lens[i - 1] + 1);
+ else
+ {
+ buf = XNEWVEC (char, lens[i - 1] + 1);
+ to_free[i - 1] = true;
+ }
+ msgs[i - 1] = buf;
+ memset (buf, 0, lens[i - 1] + 1);
+ tree field, value;
+ unsigned k;
+ unsigned HOST_WIDE_INT l = 0;
+ FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (str), k, field, value)
+ if (!tree_fits_shwi_p (value))
+ goto unhandled;
+ else if (field == NULL_TREE)
+ {
+ if (integer_zerop (value))
+ break;
+ if (l >= off && l < off + lens[i - 1])
+ buf[l - off] = tree_to_shwi (value);
+ ++l;
+ }
+ else if (TREE_CODE (field) == RANGE_EXPR)
+ {
+ tree lo = TREE_OPERAND (field, 0);
+ tree hi = TREE_OPERAND (field, 1);
+ if (!tree_fits_uhwi_p (lo) || !tree_fits_uhwi_p (hi))
+ goto unhandled;
+ if (integer_zerop (value))
+ break;
+ unsigned HOST_WIDE_INT m = tree_to_uhwi (hi);
+ for (l = tree_to_uhwi (lo); l <= m; ++l)
+ if (l >= off && l < off + lens[i - 1])
+ buf[l - off] = tree_to_shwi (value);
+ }
+ else if (tree_fits_uhwi_p (field))
+ {
+ l = tree_to_uhwi (field);
+ if (integer_zerop (value))
+ break;
+ if (l >= off && l < off + lens[i - 1])
+ buf[l - off] = tree_to_shwi (value);
+ l++;
+ }
+ buf[lens[i - 1]] = '\0';
+ }
+ }
+ if (msgs[0])
+ {
+ for (int i = 0; i < lens[0]; ++i)
+ if (!ISALNUM (msgs[0][i]) && msgs[0][i] != '_' && msgs[0][i] != '=')
+ {
+ if (!ctx->quiet)
+ error_at (loc, "%qs tag string contains %qc character other than"
+ "letters, digits, %<_%> or %<=%>",
+ "__builtin_constexpr_diag", msgs[0][i]);
+ *non_constant_p = true;
+ goto out;
+ }
+ }
+ if (ctx->manifestly_const_eval == mce_unknown)
+ goto out;
+ if (msgs[0] && lens[0])
+ {
+ char *new_option = XNEWVEC (char, lens[0] + 2);
+ new_option[0] = 'W';
+ memcpy (new_option + 1, msgs[0], lens[0]);
+ new_option[lens[0] + 1] = '\0';
+ opt_index = find_opt (new_option, CL_CXX);
+ XDELETEVEC (new_option);
+ if (opt_index != OPT_SPECIAL_unknown
+ && !(cl_options[opt_index].flags & CL_WARNING))
+ opt_index = OPT_SPECIAL_unknown;
+ }
+ if (integer_zerop (args[0]))
+ kind = diagnostics::kind::note;
+ else if (integer_onep (args[0]))
+ kind = diagnostics::kind::warning;
+ if (opt_index != OPT_SPECIAL_unknown)
+ emit_diagnostic (kind, loc, opt_index, "constexpr message: %.*s",
+ lens[1], msgs[1]);
+ else
+ emit_diagnostic (kind, loc, 0,
+ "constexpr message with tag %.*s: %.*s",
+ lens[0], msgs[0], lens[1], msgs[1]);
+ t = void_node;
+out:
+ if (to_free[0])
+ XDELETEVEC (const_cast <char *> (msgs[0]));
+ if (to_free[1])
+ XDELETEVEC (const_cast <char *> (msgs[1]));
+ return t;
+}
+
/* Attempt to evaluate T which represents a call to a builtin function.
We assume here that all builtin functions evaluate to scalar types
represented by _CST nodes. */
@@ -2322,6 +2573,10 @@ cxx_eval_builtin_function_call (const co
fun, non_constant_p, overflow_p,
jump_target);
+ if (fndecl_built_in_p (fun, CP_BUILT_IN_CONSTEXPR_DIAG, BUILT_IN_FRONTEND))
+ return cxx_eval_constexpr_diag (ctx, t, non_constant_p, overflow_p,
+ jump_target);
+
int strops = 0;
int strret = 0;
if (fndecl_built_in_p (fun, BUILT_IN_NORMAL))
--- gcc/cp/cp-tree.h.jj 2025-11-10 12:51:37.310771786 +0100
+++ gcc/cp/cp-tree.h 2025-11-10 17:19:26.109717774 +0100
@@ -6894,6 +6894,7 @@ enum cp_built_in_function {
CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS,
CP_BUILT_IN_SOURCE_LOCATION,
CP_BUILT_IN_EH_PTR_ADJUST_REF,
+ CP_BUILT_IN_CONSTEXPR_DIAG,
CP_BUILT_IN_LAST
};
@@ -9245,9 +9246,11 @@ public:
cexpr_str (const cexpr_str &) = delete;
~cexpr_str () { XDELETEVEC (buf); }
- bool type_check (location_t location);
+ bool type_check (location_t location, bool allow_char8_t = false);
bool extract (location_t location, const char * & msg, int &len);
bool extract (location_t location, tree &str);
+ tree get_data () const { return message_data; }
+ tree get_sz () const { return message_sz; }
tree message;
private:
tree message_data = NULL_TREE;
--- gcc/cp/semantics.cc.jj 2025-11-10 12:51:37.389770772 +0100
+++ gcc/cp/semantics.cc 2025-11-10 17:27:50.018735312 +0100
@@ -12493,7 +12493,7 @@ init_cp_semantics (void)
otherwise false. */
bool
-cexpr_str::type_check (location_t location)
+cexpr_str::type_check (location_t location, bool allow_char8_t /*=false*/)
{
tsubst_flags_t complain = tf_warning_or_error;
@@ -12529,7 +12529,7 @@ cexpr_str::type_check (location_t locati
if (message_sz == error_mark_node || message_data == error_mark_node)
return false;
message_sz = build_converted_constant_expr (size_type_node, message_sz,
- complain);
+ complain);
if (message_sz == error_mark_node)
{
error_at (location, "constexpr string %<size()%> "
@@ -12537,8 +12537,17 @@ cexpr_str::type_check (location_t locati
"%<std::size_t%>");
return false;
}
+
+ if (allow_char8_t
+ && POINTER_TYPE_P (TREE_TYPE (message_data))
+ && (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (message_data)))
+ == char8_type_node)
+ && (TYPE_QUALS (TREE_TYPE (TREE_TYPE (message_data)))
+ == TYPE_QUAL_CONST))
+ return true;
+
message_data = build_converted_constant_expr (const_string_type_node,
- message_data,
complain);
+ message_data, complain);
if (message_data == error_mark_node)
{
error_at (location, "constexpr string %<data()%> "
--- gcc/cp/decl.cc.jj 2025-10-29 15:26:24.636131956 +0100
+++ gcc/cp/decl.cc 2025-11-10 16:15:06.425762793 +0100
@@ -5574,6 +5574,15 @@ cxx_init_decl_processing (void)
set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
}
+ tree void_vaintftype = build_varargs_function_type_list (void_type_node,
+ integer_type_node,
+ NULL_TREE);
+ decl = add_builtin_function ("__builtin_constexpr_diag",
+ void_vaintftype,
+ CP_BUILT_IN_CONSTEXPR_DIAG,
+ BUILT_IN_FRONTEND, NULL, NULL_TREE);
+ set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
+
integer_two_node = build_int_cst (NULL_TREE, 2);
/* Guess at the initial static decls size. */
--- gcc/cp/cp-gimplify.cc.jj 2025-10-08 17:46:17.913772272 +0200
+++ gcc/cp/cp-gimplify.cc 2025-11-10 16:18:23.203064253 +0100
@@ -956,6 +956,9 @@ cp_gimplify_expr (tree *expr_p, gimple_s
"__builtin_eh_ptr_adjust_ref");
*expr_p = void_node;
break;
+ case CP_BUILT_IN_CONSTEXPR_DIAG:
+ *expr_p = void_node;
+ break;
default:
break;
}
Jakub
#include <string_view>
namespace std
{
#if __has_builtin(__builtin_constexpr_diag)
struct _S_constexpr_tag_str {
private:
string_view _M_str;
public:
template <class _Tp>
requires convertible_to<const _Tp&, string_view>
consteval _S_constexpr_tag_str(const _Tp& __s) : _M_str(__s) {}
friend constexpr void constexpr_print_str(_S_constexpr_tag_str __tag,
string_view) noexcept;
friend constexpr void constexpr_print_str(_S_constexpr_tag_str __tag,
u8string_view) noexcept;
friend constexpr void constexpr_warning_str(_S_constexpr_tag_str,
string_view) noexcept;
friend constexpr void constexpr_warning_str(_S_constexpr_tag_str,
u8string_view) noexcept;
friend constexpr void constexpr_error_str(_S_constexpr_tag_str,
string_view) noexcept;
friend constexpr void constexpr_error_str(_S_constexpr_tag_str,
u8string_view) noexcept;
};
constexpr void constexpr_print_str(string_view __msg) noexcept
{ return __builtin_constexpr_diag(0, "", __msg); }
constexpr void constexpr_print_str(u8string_view __msg) noexcept
{ return __builtin_constexpr_diag(0, "", __msg); }
constexpr void constexpr_print_str(_S_constexpr_tag_str __tag,
string_view __msg) noexcept
{ return __builtin_constexpr_diag(0, __tag._M_str, __msg); }
constexpr void constexpr_print_str(_S_constexpr_tag_str __tag,
u8string_view __msg) noexcept
{ return __builtin_constexpr_diag(0, __tag._M_str, __msg); }
constexpr void constexpr_warning_str(_S_constexpr_tag_str __tag,
string_view __msg) noexcept
{ return __builtin_constexpr_diag(1, __tag._M_str, __msg); }
constexpr void constexpr_warning_str(_S_constexpr_tag_str __tag,
u8string_view __msg) noexcept
{ return __builtin_constexpr_diag(1, __tag._M_str, __msg); }
constexpr void constexpr_error_str(_S_constexpr_tag_str __tag,
string_view __msg) noexcept
{ return __builtin_constexpr_diag(2, __tag._M_str, __msg); }
constexpr void constexpr_error_str(_S_constexpr_tag_str __tag,
u8string_view __msg) noexcept
{ return __builtin_constexpr_diag(2, __tag._M_str, __msg); }
#endif
}
consteval
{
std::constexpr_print_str("foo");
std::constexpr_print_str(u8"bar");
std::constexpr_print_str("uninitialized", "foo");
std::constexpr_print_str("uninitialized", u8"bar");
std::constexpr_warning_str("uninitialized", "foo");
std::constexpr_warning_str("uninitialized", u8"bar");
std::constexpr_error_str("uninitialized", "foo");
std::constexpr_error_str("uninitialized", u8"bar");
}
struct S {
char buf[16];
constexpr const char *data () const { return buf; }
constexpr decltype (sizeof 0) size () const { for (int i = 0; i < 16; ++i) if
(!buf[i]) return i; return 0; }
};
struct T {
constexpr const char *data () const { return "bar"; }
constexpr decltype (sizeof 0) size () const { return 3; }
};
constexpr char str[] = "abcdefg";
struct U {
constexpr const char *data () const { return &str[2]; }
constexpr decltype (sizeof 0) size () const { return 4; }
};
struct V {
constexpr const char *data () const { return &"abcdefghi"[3]; }
constexpr decltype (sizeof 0) size () const { return 5; }
};
struct W {
constexpr const char *data () const { return &"abcdefghi"[3] + 2; }
constexpr decltype (sizeof 0) size () const { return 3; }
};
consteval
{
S s;
for (int i = 0; i < 10; ++i)
s.buf[i] = '0' + i;
s.buf[10] = '\0';
__builtin_constexpr_diag (0, "foo", "bar");
__builtin_constexpr_diag (1, "foo", "bar");
__builtin_constexpr_diag (2, "foo", "bar");
__builtin_constexpr_diag (0, "format=", "bar");
__builtin_constexpr_diag (1, "format=", "bar");
__builtin_constexpr_diag (2, "format=", "bar");
__builtin_constexpr_diag (0, "baz", s);
__builtin_constexpr_diag (1, "baz", T {});
__builtin_constexpr_diag (2, "baz", U {});
__builtin_constexpr_diag (0, "baz", V {});
__builtin_constexpr_diag (1, "baz", W {});
}