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

Можно ли эмулировать ключевое слово C, используя строгий псевдоним в С++?

Проблема

Ключевое слово restrict в C отсутствует в С++, поэтому из интереса я искал способ эмуляции одной и той же функции на С++.

В частности, я хотел бы, чтобы следующее было эквивалентным:

// C
void func(S *restrict a, S *restrict b)

// C++
void func(noalias<S, 1> a, noalias<S, 2> b)

где noalias<T, n>

  • ведет себя так же, как T* при доступе с помощью -> и *
  • может быть построено из T* (так что эту функцию можно вызывать как func(t1, t2), где t1 и t2 - оба типа T*)
  • индекс n указывает "класс псевдонимов" переменной, так что переменные типа noalias<T, n> и noalias<T, m> могут быть предположены никогда не псевдониму для n!= m.

Попытка

Вот мое глубоко ошибочное решение:

template <typename T, int n>
class noalias
{
    struct T2 : T {};
    T *t;

public:
    noalias(T *t_) : t(t_) {}
    T2 *operator->() const {return static_cast<T2*>(t);} // <-- UB
};

При доступе с помощью -> он заносит внутренне сохраненный T* в noalias<T, n>::T2* и возвращает это вместо этого. Поскольку для каждого n это другой тип, строгое правило сглаживания гарантирует, что он никогда не будет псевдонимом. Кроме того, поскольку t2 происходит от T, возвращаемый указатель ведет себя как a T*. Отлично!

Еще лучше, компиляция кода и выход сборки подтверждают, что он имеет желаемый эффект.

Проблема заключается в static_cast. Если T действительно указывали на объект типа t2, тогда это было бы хорошо. Но T указывает на a T, так что это UB. На практике, поскольку t2 является подклассом, который ничего не добавляет к T, он, вероятно, будет иметь тот же макет данных, и поэтому доступ членов к T2* будет искать элементы с одинаковыми смещениями, как они встречаются в T и все будет хорошо.

Но для строгого сглаживания необходим n -зависимый класс и этот класс вытекает из T, так что указатель можно рассматривать как a T*. Поэтому UB кажется неизбежным.

Вопросы

  • Можно ли это сделать в С++ 14 без вызова UB - возможно, используя совершенно другую идею?

  • Если нет, то я слышал о "точечном операторе" в С++ 1z; было бы возможно с этим?

  • Если выше, будет ли что-то похожее на noalias появляться в стандартной библиотеке?

4b9b3361

Ответ 1

Вы можете использовать расширение __restrict__ GCC для un/aliasing.

Из документов

В дополнение к разрешению ограниченных указателей вы можете указать ограниченные ссылки, которые указывают, что ссылка не сглажена в локальном контексте.

void fn (int *__restrict__ rptr, int &__restrict__ rref)
{
/* ... */
}

В теле fn, rptr указывает на unaliased целое число, а rref ссылается на (различное) недостижимое целое число. Вы также можете указать, является ли функция-член этого указателя неубежденным, используя __restrict__ в качестве квалификатора функции-члена.

void T::fn () __restrict__
{
/* ... */
}

В теле T::fn это будет иметь эффективное определение T *__restrict__ const this. Обратите внимание, что интерпретация спецификатора функции __restrict__ отличается от интерпретатора const или volatile, поскольку он применяется к указателю, а не к объекту. Это согласуется с другими компиляторами, которые реализуют ограниченные указатели.

Как и во всех внешних классификаторах параметров, __restrict__ игнорируется при сопоставлении определения функции. Это означает, что вам нужно указывать только __restrict__ в определении функции, а не в прототипе функции.

Ответ 2

Может быть, я не понимаю ваш вопрос, но c ограничение ключевого слова было удалено из STANDARD С++, но почти у каждого компилятора есть свои эквиваленты C-ограничения:

Microsoft VS имеет __declspec (ограничение): https://msdn.microsoft.com/en-us/library/8bcxafdh.aspx

и GCC имеет ограничение __: https://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html

Если вы хотите использовать общее определение, вы можете использовать # define

#if defined(_MSC_VER)
#define RESTRICT __declspec(restrict)
#else
#define RESTRICT __restrict__
#endif

Я не тестирую его, дайте мне знать, что это не работает.

Ответ 3

Если мы говорим только о чистом стандартном решении С++, проверка времени выполнения - единственный способ. Я даже не уверен, что это возможно, учитывая силу определения C ограничения lvalue, который заключается в том, что объект может быть доступен только указателем ограничения.

Ответ 4

Правильный способ добавления ограничительной семантики к С++ заключался бы в том, чтобы Standard определял шаблоны для ограниченных ссылок и ограниченных указателей таким образом, чтобы фиктивные версии, которые работают как обычные ссылки и указатели, могли быть закодированы на С++. Хотя возможно создание шаблонов, которые ведут себя по мере необходимости во всех определенных случаях, и вызывать UB во всех случаях, которые не должны быть определены, это будет бесполезно, если не контрпродуктивно, если компилятор не запрограммирован на использование UB, о котором идет речь, чтобы облегчить такие оптимизации. Программирование компилятора для использования таких оптимизаций в случаях, когда код использует стандартный тип, который существует для этой цели, может быть проще и эффективнее, чем пытаться идентифицировать шаблоны в пользовательских типах, где он будет использоваться, а также будет менее вероятным иметь нежелательные побочные эффекты.

Ответ 5

Я думаю, что ваше решение не полностью достигает намеченной цели, даже если указанный UB не существует. В конце концов, все реальные обращения к данным происходят на встроенном уровне типа. Если decltype(a->i) - int, и ваша функция управляет указателями int*, то при определенных обстоятельствах компилятор должен все же предположить, что эти указатели могут быть псевдонимом a->i.

Пример:

int func(noalias<S, 1> a) {
    int s = 0;
    int* p = getPtr();
    for ( int i = 0; i < 10; ++i ) {
        ++*p;
        s += a->i;
    }
    return s;
}

Использование noalias вряд ли позволит оптимизировать вышеуказанную функцию следующим образом:

int func(noalias<S, 1> a) {
    *getPtr() += 10;
    return 10 * a->i;
}

Я чувствую, что restrict нельзя эмулировать и должен поддерживаться непосредственно компилятором.