Подтвердить что ты не робот

Функция, которая принимает только целые литералы

Я ищу способ моделирования некоторых перегруженных встроенных модулей GCC на С++. Встроенные модули схожи с ними:

__builtin_foo(char *a, signed int b);
__builtin_foo(short *a, signed int b);
__builtin_foo(long *a, signed int b);

С особым ограничением, жестко закодированным в GCC: b должно быть буквальным значением, то есть вы можете вызвать:

__builtin_foo((char *)0, 1);

но не:

extern int val;
__builtin_foo((char *)0, val);

который генерирует ошибку компилятора. Я искал с помощью std::enable_if, чтобы имитировать это, но не могу найти способ обеспечить, чтобы принимались только литералы. Есть ли способ сделать это?

4b9b3361

Ответ 1

Вы можете сортировать его с помощью макроса и встроенного GCC __builtin_constant_p

constexpr int foo(int i) { return i; }

#define FOO(i) do { \
  static_assert(__builtin_constant_p(i), "Not a constant"); \
  foo(i); \
} while (false)

Это позволит FOO(1) скомпилировать, но не int i = 1; FOO(i);

Однако результат __builtin_constant_p зависит от уровня оптимизации, при более высоких уровнях оптимизации const переменные рассматриваются как константы, поэтому он принимает не только литералы.

Конечно, если вы готовы разрешать постоянные выражения, а не только литералы, то все, что вам нужно сделать, это использовать переменную в контексте, который требует постоянного выражения, например статического утверждения или не-типа аргумент шаблона.

Ответ 2

Вот портативное решение на С++ 11, так что ваша функция (макрос фактически, извините) принимает только целые литералы и вместо этого запускает ошибку времени компиляции:

constexpr int operator "" _literal(unsigned long long i)
{
    return i;
}

#define m_builtin_foo(integer) builtin_foo(integer ## _literal)

Пользовательские литералы принимают только литералы (отсюда и их название). Поэтому, если вы сделаете свой макрос вставляемым пользователем литералом для того, что ему передается, он должен принимать только литералы, принятые соответствующим пользовательским литералом.

Однако, это довольно уродливо, и я думаю, что это может быть подвержено ошибкам. Не знаю, как это сделать.


@Майкл Андерсон указал на комментарии, что m_builtin_foo(i+1) будет работать. Это так. @Angew предложила обернуть возвращаемый тип _literal в оболочку, несовместимую с целочисленной арифметикой и добавив явные преобразования. Он был прав, вот более полное решение:

struct wrapper
{
    constexpr wrapper(int n):
        value{n}
    {}

    explicit constexpr operator int() const
    {
        return value;
    }

    int value;
};

constexpr wrapper operator "" _literal(unsigned long long i)
{
    return { int(i) };
}

#define m_builtin_foo(integer) builtin_foo(int(integer ## _literal))

Ответ 3

Вы можете превратить функцию в шаблон:

template <int b>
builtin_foo(char *a);

Тогда синтаксис вызова будет builtin_foo<1>(whatever).

Если вам это не нравится, вы можете обернуть шаблон в макрос:

#define m_builtin_foo(a, b) builtin_foo<(b)>((a))

Это, конечно, будет принимать любое интегральное постоянное выражение, а не только литерал.