How to print non-structural results of constexpr functions at compile time with clang++?
Asked Answered
R

1

6

I'm writing a constexpr code and I would like to inspect computed values at compile time.

The usual trick is to do something like this:


struct ReturnValue
{
    int value1;
    int value2;
};

constexpr ReturnValue doTheComputation() 
{
    return { .value1 = 10, .value2 = 20 };
}


template< auto t > struct p;

p< doTheComputation() > foo;

This gives a nice compile error:

implicit instantiation of undefined template 'p<ReturnValue{10, 20}>'

and I can see the value of the result.

However, this does not work for non-structural types, like std::string_view, std::string, or anything that resembles a string, as such types are not allowed to be NTTP.

Non-working example with std::string_view: https://godbolt.org/z/da8W557nK

Non-working example with char const *: https://godbolt.org/z/5Mvqfx95q

Even if I use char const[ some_large_numer ] to ensure the message fits, like this, instead of a string, I get the ASCII values listed as template parameters:

implicit instantiation of undefined template 'p<ReturnValue{{115, 111, 109, 101, 32, 115, 101, 99, 114, 101, ...}}>

Are there any tips or tricks to print values of non-structural types at compile-time (as results of constexpr functions)?

I've seen an unofficial patch for GCC that apparently solves that (I haven't tested it), but I'm searching for a solution for clang (Apple-clang from Xcode 15 or newer and LLVM 18 or newer).

Rima answered 26/6 at 11:27 Comment(10)
Unrelated: You can reduce return {.value1 = 10, .value2 = 20} to just return {10,20}Stasny
Just add a proper formatter and use p<std::format("{}", doTheComputation())>.Crew
Thank you for the trick, sometimes I need quick way to compute sizeof() or offsetof.Breakable
@WeijunZhou, the trick with std::format does not work for the same reason I mentioned in the original question - std::format returns std::string, which is not structural type, hence cannot be used as a NTTP.Rima
@Stasny - I know that. In general, using {10, 20} is considered a bad practice as it's ambiguous.Rima
@Rima There's nothing ambiguous about it. Whoever told you that it could cause ambiguity was lying as the order is already fixed when implemented the class. In your example, value1 is declared before value2 and this is the only way they will be initialized in {10, 20}. There's no ambiguity about it whatsoever. It may be helpful/useful but not because it removes any ambiguity.Stasny
@Stasny It is still helpful to a human reader if the definition of ReturnValue is far away (for example in another file). I prefer being explicit.Crew
@WeijunZhou You still need to need to know that names of the members to write in the designator list and for that one still have to see/lookup the class implementation. To the reader it will be helpful, yes.Stasny
I mean for a reader, not a writer. return Rectangle{.width=3, .height=4}; is clearer than return Rectangle{3,4} for someone that reads the code. I'm not talking about writing it.Crew
@WeijunZhou Yes for reader I agree. I see you updated your comment. I've also updated my comment that I agree it will be helpful to the reader.Stasny
R
3

Inspired by the idea by @WeijunZhou from comments to the question, I searched more. I discovered that C++26 has a flavor of static_assert that allows the second parameter to be a string-like result of constexpr computation (C++ reference).

Unfortunately, std::format is not constexpr-friendly even in C++26 (cpp-reference confirms this), but I can always write my formatting function that outputs string-like literal object (according to the specification, it only needs to provide .size() and .data() functions), so I came up with the following solution:

#include <string_view>
#include <string>

struct ReturnValue
{
    std::string_view data;
    std::string      data2;
};

constexpr ReturnValue doTheComputation() 
{
    return { .data = "some secret message", .data2 = "owned data" };
}

constexpr std::string format( ReturnValue const & val )
{
    std::string result{ val.data };
    result += ", additional data: ";
    result += val.data2;
    return result;
}

static_assert( false, format( doTheComputation() ) );

This appears to work on LLVM 18, but not yet on Apple-Clang shipped with Xcode 15.4 (I still need to test with Xcode 16 beta).

In general, it's a workable solution, but I would still be happier if somebody came up with a solution that I could use on Xcode 15.4.

Rima answered 26/6 at 15:24 Comment(1)
Apparently, this feature will not be ready in Xcode 16, according to this Apple document.Rima

© 2022 - 2024 — McMap. All rights reserved.