8

I have a user-defined literal operator that only makes sense for strings of a specific length, like this:

constexpr uint16_t operator "" _int(const char* s, std::size_t len)
{
    return len == 2 ? s[0] | (s[1] << 8) : throw;
}

This works:

"AB"_int // equals 16961

But this also compiles, and I don't want it to:

"ABC"_int // throws at runtime

I tried static_assert(len == 2), but it isn't allowed in a constexpr function.

How can I make "ABC"_int cause an error at compile time?

John Zwinck
  • 239,568
  • 38
  • 324
  • 436

5 Answers5

2

How can I make "ABC"_int cause an error at compile time?

By example: initialize a constexpr variable

constexpr auto foo = "ABC"_int;

Otherwise (if you don't force the compile time calculation in some way) the compiler doesn't compute (not mandatory but, in fact, is what happens) compile time but prepare the code for the run-time compilation.

max66
  • 65,235
  • 10
  • 71
  • 111
  • @MarkRansom - D'Oh! Thanks. – max66 Nov 29 '17 at 01:39
  • This doesn't answer the question. It's useful info that the OP apparently was unaware of, but it's not an answer. It should be a comment; flagged as such. – Cheers and hth. - Alf Nov 29 '17 at 02:19
  • @Cheersandhth.-Alf - It seems to me that respond to the question; clearly if the OP intend "How can I make "ABC"_int cause **ever** an error at compile time?", doesn't give a solution but explain (It seems to me clear) that it's impossible. Waiting for the moderator action. – max66 Nov 29 '17 at 02:56
  • 2
    I realize that using the value in a `constexpr` will make `"ABC"_int` fail to compile, but I want `"ABC"_int` to fail to compile even if used outside a `constexpr`. Another way of stating it is that I want the operator to be evaluated as `constexpr` always, and if it can't be, it should fail to compile. – John Zwinck Nov 29 '17 at 03:00
  • 1
    @JohnZwinck - Sorry: in this form (literal operator for char const * and size) I don't think it's possible (at the moment). If you were interested in numbers (something `12_int` instead of `"AB"_int`) there was the template form for string literal (so, by example, a `static_assert()`). But I suppose you're interested also in all chars. – max66 Nov 29 '17 at 03:17
1

Before C++20, you can wrap std::integral_constant with a macro to make throw trigger a compile error.

    constexpr uint16_t operator "" _int(const char* s, std::size_t len)
    {
        return len == 2 ? s[0] | (s[1] << 8) : throw;
    }

    void test()
    {
#define FORCE_CONSTANT(val) std::integral_constant<decltype(val), (val)>::value

        FORCE_CONSTANT("AB"_int);
        // FORCE_CONSTANT("ABC"_int); // error, expected compile-time constant expression
    }

And things become easy since C++20, user-defined literals are allowed to be string literal operator template. (cppref)

So, the following code will work as you expect.

template <size_t kCount>
struct template_str_buffer
{
    using char_type = char;

    consteval template_str_buffer(const char_type(&str)[kCount]) noexcept
    {
        for (size_t i = 0; i < kCount; ++i) {
            data[i] = str[i];
        }
    }

    char_type data[kCount];
    constexpr static size_t count = kCount - sizeof(char_type);
};

template <template_str_buffer kStrBuf>
consteval uint16_t operator""_int()
{
    static_assert(kStrBuf.count == 2);
    return kStrBuf.data[0] | (kStrBuf.data[1] << 8);
}

void test()
{
    "AB"_int;
    // "ABC"_int; // static assertion failed
}
Sprite
  • 3,222
  • 1
  • 12
  • 29
1

With C++20 you can use consteval and a normal assert (or whatever exception you like):

#include <iostream>
#include <cstdint>
#include <cassert>

consteval uint16_t operator "" _int(const char* s, size_t len)
{
    assert(len == 2);
    return s[0] | (s[1] << 8);
}

int main() {
    std::cout << "AB"_int << std::endl;
    //std::cout << "ABC"_int << std::endl;  // compiler error

    return 0;
}
snooper77
  • 184
  • 1
  • 5
  • I tagged the question as C++11 back in 2017 because user-defined literals were introduced in C++11. I'm happy to see a C++20 solution, thank you. – John Zwinck Aug 11 '23 at 09:49
0
#include <iostream>
#include <cstdint>
using namespace std;

constexpr uint16_t operator "" _int(char const * s, size_t len)
{
    return (len == 2) ? s[0] | (s[1] << 8) : throw "len must be 2!";
}

int main()
{
    constexpr uint16_t i1 = "AB"_int; // OK
    cout << i1 << endl; // outputs 16961

    constexpr uint16_t i2 = "ABC"_int; // error
    cout << i2 << endl;

    return 0;
}

prog.cpp: In function ‘int main()’:
prog.cpp:13:29:   in constexpr expansion of ‘operator""_int(((const char*)"ABC"), 3ul)’
prog.cpp:7:52: error: expression ‘<throw-expression>’ is not a constant-expression
     return (len == 2) ? s[0] | (s[1] << 8) : throw "len must be 2!";
                                                    ^~~~~~~~~~~~~~~~

Live Demo

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 3
    This doesn't answer the question. It's useful info that the OP apparently was unaware of, but it's not an answer. It should be a comment; flagged as such. – Cheers and hth. - Alf Nov 29 '17 at 02:38
  • 2
    @Cheersandhth.-Alf It DOES answer the question asked: "*How can I make `"ABC"_int` cause an error at compile time?*". Did you even try it? The error message is not nice looking, but it does only occur on `"ABC"_int`, `"AB"_int` compiles fine and outputs the desired output – Remy Lebeau Nov 29 '17 at 02:39
  • 2
    The question is "How can I make "ABC"_int cause an error at compile time". Your attempted answer is that in one specific context, that of initializing a `constexpr` value, it causes error at compile time. The OP clearly asked for a general mechanism, not one particular special context. – Cheers and hth. - Alf Nov 29 '17 at 02:41
  • So, you should *explain* what you have expressed with code (the single special context you could think of), and post that as a *comment*, and *delete* this attempted answer. – Cheers and hth. - Alf Nov 29 '17 at 02:43
0

This was unfortunately not practical to post as a comment.

Fixes apart from making ungood literal a compile time error:

  • Shift of signed values fixed.
  • Use of throw without arguments.
  • Assumption of 8-bit byte made explicit.
#include <iostream>
#include <stdint.h>
#include <limits.h>         // CHAR_BIT
using namespace std;

using Byte = unsigned char;
const int bits_per_byte = CHAR_BIT;

static_assert( bits_per_byte == 8, "!" );

constexpr auto operator "" _int( char const* s, std::size_t len )
    -> uint16_t 
{ return len == 2 ? Byte( s[0] ) | (Byte( s[1] ) << 8u) : throw "Bah!"; }

#define CHAR_PAIR( s ) static_cast<uint16_t>( sizeof( char[s ## _int] ) )

auto main()
    -> int
{
    CHAR_PAIR( "AB" );              // OK
    CHAR_PAIR( "ABC" );             //! Doesn't compile as ISO C++.
}

With Visual C++ that's all that's needed.

g++ is less standard-conforming in this respect, so for that compiler add option -Werror=vla.

With g++ you can alternatively use the following macro:

#define CHAR_PAIR( s ) []() constexpr { constexpr auto r = s##_int; return r; }()

This gives a more informative error message, but isn't supported by Visual C++ 2017.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • So you're relying on the `throw` being an invalid expression for the ternary? That's clever. – Mark Ransom Nov 29 '17 at 03:17
  • On second glance, that's exactly what OP is doing. How is yours different? – Mark Ransom Nov 29 '17 at 03:25
  • @Mark: The three differences for the `operator""_int` implementation are listed in bullet points. The code shows how to get the compile time checking that the OP wants, but unfortunately not with his preferred syntax. That's why this isn't an answer either (a literal answer would just be, “Sorry, that's not possible”), but it's unfortunately not practical to cram all this into an SO comment. – Cheers and hth. - Alf Nov 29 '17 at 03:28