Можно ли эмулировать ключевое слово 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
появляться в стандартной библиотеке?
Ответы
Ответ 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
нельзя эмулировать и должен поддерживаться непосредственно компилятором.