C11 _Generic: как работать со строковыми литералами?

Используя функцию _Generic в C11, как вы справляетесь со строковыми литералами?

Например:

#include <stdio.h>
#define foo(x) _Generic((x), char *: puts(x))

int main()
{
    foo("Hello, world!");
    return 0;
}

дает эту ошибку на clang:

controlling expression type 'char [14]' not compatible with any generic association type

Замена char * на char[] дает мне

error: type 'char []' in generic association incomplete

Единственные способы (насколько мне известно) получить это для компиляции:

  1. Приведите строковый литерал к соответствующему типу. Это уродливо и (на мой взгляд) в первую очередь противоречит пункту _Generic.
  2. Используйте char[14] в качестве спецификатора типа. Вы, должно быть, должны шутить надо мной...

Я предполагал, что массивы будут распадаться на указатели при передаче в _Generic, но, очевидно, это не так. Итак, как делать использование _Generic со строковыми литералами? Это единственные два варианта?

Я использую clang 3.2 в Debian. К сожалению, это единственный доступный мне компилятор, который поддерживает эту функцию, поэтому я не могу сказать, является ли это ошибкой компилятора или нет.


person Michael Rawson    schedule 17.09.2013    source источник
comment
Обратите внимание, что проект N2176 для предстоящего C17 требует перехода от массива к указателю управляющего выражения общего выбора в 6.5.1.1, поэтому строковые литералы будут рассматриваться как char*: Тип управляющего выражения — это тип выражения, как если бы оно подверглось преобразование lvalue (сноска: преобразование lvalue удаляет квалификаторы типа), преобразование массива в указатель или преобразование функции в указатель.   -  person dpi    schedule 14.07.2018


Ответы (3)


Вот решение:

#include <stdio.h>
#define foo(x) _Generic((0,x), char*: puts(x))

int main()
{
    foo("Hello, world!");
    return 0;
}

Это компилирует и производит:

$ clang t.c && ./a.out 
Hello, world!

Это несколько хромает, но я не нашел лучшего способа заставить x распадаться на указатель на char или сопоставлять его тип нечетким способом, который вам нужен, с Apple LLVM версии 4.2 (clang-425.0.28) (на основе ЛЛВМ 3.2СВН).

Согласно этой записи в блоге Йенса Густедта поведение GCC отличается (по-видимому, в GCC строки автоматически распадаются на указатель в контексте _Generic).

Кстати, в C типом строкового литерала является массив char, а не const char. Отклонение char [] как имя-типа в универсальной-ассоциации не является ошибкой компилятора:

Общий выбор должен иметь не более одной общей ассоциации по умолчанию. Имя типа в универсальной ассоциации должно указывать полный тип объекта, отличный от изменяемого типа. (6.5.1.1:2 с моим акцентом)

person Pascal Cuoq    schedule 17.09.2013
comment
Спасибо, неплохое предложение, если раздражает. Насчет char против const char: да, но _Generic нечетко добавляет const, так что я решил воспользоваться этим. Я отредактирую пример. Ваше здоровье! - person Michael Rawson; 17.09.2013
comment
@MichaelRawson 6.5.1.1 продолжает: «Если общий выбор имеет общую ассоциацию с именем типа, который совместим с типом управляющего выражения, то результирующее выражение общего выбора — это выражение в эта родовая ассоциация». и const char * не совместим с char *. Я бы не ожидал никакой const-fuzziness. Мой clang не позволяет мне никаких. - person Pascal Cuoq; 17.09.2013
comment
мои извинения, я, должно быть, где-то пропустил звездочку или что-то. clang ведет себя так, как вы (и стандарт) описываете. - person Michael Rawson; 17.09.2013

Я нашел способ избежать хитрого трюка (0,x).

Если вы используете строковый литерал, тип — char[s] , где s — размер строкового литерала.

Как получить такой размер? Используйте оператор sizeof:

#include <stdio.h>

#define Test( x )   _Generic( ( x ) ,   char*: puts ,                   \
                                        const char*: puts ,             \
                                        const char[sizeof( x )]: puts , \
                                        char[sizeof( x )]: puts )( x )

int main(void) 
{

    char str[] = "This" ;
    Test( str ) ;

    Test( "works" ) ;

    char str2[10] = "!!!" ;
    Test( str2 ) ;

return 0;
}

Я попытался скомпилировать его с помощью clang и Pelles, и это сработало.

Единственная проблема, которую вам все еще нужно приводить к массивам переменной длины.

Попробовав еще немного, я нашел другой аналогичный способ сделать то, что сделал Pascal Cuoq, используя &* операторы:

#include <stdio.h>
#define foo(x) _Generic( ( &*(x) ), char*: puts , const char*: puts )( x )

int main()
{
    foo("Hello, world!");
    return 0;
}
person 2501    schedule 10.11.2014
comment
&* — это еще один «ничего не делающий» контекст, который, как и 0,, вызывает затухание указателя, но он не будет работать, если x не является указателем (скажем, если вы хотите использовать _Generic как для вещественных, так и для строковых литералов). Я очень впечатлен тем, как вы используете sizeof. - person Pascal Cuoq; 11.11.2014
comment
@PascalCuoq Вы правы, (0, x) - единственный способ. Я сделал все это только потому, что компилятор выдает мне предупреждение: Выражение без эффекта удалено. если я использую (0,x). - person 2501; 11.11.2014
comment
Хорошо, если оператор, который предшествует _Generic, оказывается выражением, вы можете переместить его в _Generic, преобразовав, скажем, printf("hello world\n"); _Generic(x, …) в _Generic((printf("hello world\n"), x), …). Вы также можете попробовать понять, делает ли (((void)0), x) намерение понятным для компилятора. - person Pascal Cuoq; 11.11.2014
comment
@PascalCuoq Да, ( void )0 сделал это. Я также попробовал ваш способ на clang онлайн gcc.godbolt.org, и он выдал ошибку, но изменив выражение немного: _Generic( ( (0,x)+0 ) решил проблему. - person 2501; 11.11.2014
comment
Разве *& не будет работать правильно для всех типов, в том числе и без указателя? - person psprint; 30.12.2020

Поведение Clang было неправильным< /strong> (отчет о дефекте C11 481) до 3.7.1. Ошибка была исправлена ​​в Clang 3.8.0, выпущенном 8 марта 2016 г..

В ответе Комитета на DR 481 говорится следующее:

Эта статья вызвала долгую и продуктивную дискуссию. Комитет согласен с автором _Generic предложения в том, что намерение состояло в том, чтобы явно избегать выбора по квалифицированным типам, как и выбора массивов по размеру. Цель _Generic состояла в том, чтобы дать C механизм для некоторого выражения понятия «перегруженная функция», встречающегося в C++, и, в частности, возможный механизм, который разработчики могут использовать для реализации универсальных функций атомарного типа из раздела 7.17.7.

person Antti Haapala    schedule 28.03.2019