25

The Problem

The restrict keyword in C is missing in C++, so out of interest I was looking for a way to emulate the same feature in C++.

Specifically, I would like the following to be equivalent:

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

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

where noalias<T, n>

  • behaves just like T* when accessed with -> and *
  • can be constructed from an T* (so that the function can be called as func(t1, t2), where t1 and t2 are both of type T*)
  • the index n specifies the "aliasing class" of the variable, so that variables of type noalias<T, n> and noalias<T, m> may be assumed never to alias for n != m.

An Attempt

Here is my deeply flawed solution:

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
};

When accessed with ->, it casts the internally-stored T* to a noalias<T, n>::T2* and returns that instead. Since this is a different type for each n, the strict aliasing rule ensures that they will never alias. Also, since T2 derives from T, the returned pointer behaves just like a T*. Great!

Even better, the code compiles and the assembly output confirms that it has the desired effect.

The problem is the static_cast. If t were really pointing to an object of type T2 then this would be fine. But t points to a T so this is UB. In practice, since T2 is a subclass which adds nothing extra to T it will probably have the same data layout, and so member accesses on the T2* will look for members at the same offsets as they occur in T and everything will be fine.

But having an n-dependent class is necessary for strict aliasing, and that this class derives from T is also necessary so that the pointer can be treated like a T*. So UB seems unavoidable.

Questions

  • Can this be done in c++14 without invoking UB - possibly using a completely different idea?

  • If not, then I have heard about a "dot operator" in c++1z; would it be possible with this?

  • If the above, will something similar to noalias be appearing in the standard library?

PBS
  • 1,389
  • 11
  • 20
  • 1
    you may want to look herhe [restrict-like semantics for C++](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3635.pdf) – Matt. Stroh Jun 18 '16 at 20:25
  • Restrict is a keyword. You cannot implement it just as you can't implement 'auto' yourself – user3104201 Jun 18 '16 at 20:56
  • 3
    I would have written "emulate the behaviour of using possibly different syntax" of in the question title instead of "emulate", but I like to keep things brief and hope people fill in the gaps sensibly. – PBS Jun 18 '16 at 21:03
  • 1
    "*In practice, since T2 is a subclass which adds nothing extra to T it will probably have the same data layout*" Unless the `T*` is actually a `D*`, where `D` is a class derived from `T`. – Nicol Bolas Jun 19 '16 at 01:28
  • Also, how could `operator.` possibly help? – Nicol Bolas Jun 19 '16 at 01:31
  • 1
    Fair point about `D*` - it would also cause problems with `dynamic_cast` (you'd have to cast to `T*` inbetween). But I don't see how there could be any data layout differences for the `T`-substructure of `T2` and `D`. And looking into `operator.`, yes - it won't be of much help. – PBS Jun 19 '16 at 03:23
  • In C++, the usual technique is to avoid passing a pointer at all, instead passing something by value, where that that "something" has value semantics. This means (among other things) that two instances of a class type (if passed by value) don't share a pointer (e.g. all member functions ensure that data is not shared between two instances) or - if they do share data - that they prevent the effects of that being visible outside the class. – Peter Jul 01 '16 at 23:14
  • @peter But if I have `void foo(thing& b, thing& a)` there is no guarantee that `a` and `b` are separate objects, even though references are used – DarthRubik Jul 02 '16 at 00:05
  • That is passing by reference, Darth, not by value. It is possible, however, for your `foo()` to check the addresses - albeit that is a runtime check, not a compile time check. But passing by value does involve creating distinct objects - assuming the type implements value semantics. – Peter Jul 02 '16 at 00:11
  • @Peter sorry...my mistake... But passing by value does not help optimise, which is the main reason for wanting this (I would think) – DarthRubik Jul 02 '16 at 00:37
  • @DarthRubik Passing by value is actually great for the optimizer, Chandler Carruth has talked about this quite a bit. The problem is that if the struct is large then the usual concerns about expensive copies arise. But for primitives, just passing by value is a very simple way to avoid all these aliasing issues. – Nir Friedman Jul 02 '16 at 20:40
  • Are you doing this to allow additional optimizations? If so, your compiler almost certainly has a non-standard restrict-like keyword. It could be wise to try it out and see if it leads to actual improvements before trying to trick your compiler into applying strict aliasing. – zneak Jul 04 '16 at 23:58
  • @zneak The point is to provide a cross compiler trick to allow optimizations. The problem with using the built in `restrict` of the compiler is that many compilers implement scenarios that are not seen in `C` differently than one another.....this is a real problem when trying to port code. – DarthRubik Jul 05 '16 at 00:22
  • @DarthRubik, I realize that. I'm saying "you should test with your compiler's keyword to see if it's worth implementing a cross-compiler strict aliasing hack". – zneak Jul 05 '16 at 00:27
  • @zneak But a cross-compiler strict aliasing hack would be sooooo cool! – DarthRubik Jul 05 '16 at 00:37

5 Answers5

1

You could use the __restrict__ GCC extension for un/aliasing.

From the docs

In addition to allowing restricted pointers, you can specify restricted references, which indicate that the reference is not aliased in the local context.

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

In the body of fn, rptr points to an unaliased integer and rref refers to a (different) unaliased integer. You may also specify whether a member function's this pointer is unaliased by using __restrict__ as a member function qualifier.

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

Within the body of T::fn, this will have the effective definition T *__restrict__ const this. Notice that the interpretation of a __restrict__ member function qualifier is different to that of const or volatile qualifier, in that it is applied to the pointer rather than the object. This is consistent with other compilers which implement restricted pointers.

As with all outermost parameter qualifiers, __restrict__ is ignored in function definition matching. This means you only need to specify __restrict__ in a function definition, rather than in a function prototype as well.

Edison
  • 11,881
  • 5
  • 42
  • 50
  • 1
    Yes, but using extensions like `__restrict__` is kind of missing the point of the question, which is about accomplishing the same effect by some trick within standard C++. – PBS Jul 09 '16 at 00:43
  • Thought you were just worried about runtime. Didn't realize you needed a native solution. – Edison Jul 09 '16 at 00:52
0

Maybe i dont understand your question, but c restrict keyword was removed from STANDARD C++, but almost each compiler has their "C restrict" equivalents:

Microsoft VS has __declspec(restrict): https://msdn.microsoft.com/en-us/library/8bcxafdh.aspx

and GCC has __ restrict__ : https://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html

If you want a common definition you could use #define's

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

I dont test it, let me know is that does not work

Ing. Gerardo Sánchez
  • 1,607
  • 15
  • 14
  • Problem is when you have scenarios that are not in the original `C` language.....differences as to how those are implemented disrupt portability. – DarthRubik Jul 04 '16 at 23:53
0

If we're only talking about pure C++ Standard solution, runtime check is the only way. I'm actually not even sure if this is possible given the strength in the definition of C's restrict lvalue qualifier, which is that the object can only be accessed by the restrict pointer.

kchoi
  • 473
  • 1
  • 4
  • 11
-1

The proper way to add restrict-like semantics to C++ would be to have the Standard define templates for restricted references and restricted pointers in such a way that dummy versions which work like ordinary references and pointers could be coded in C++. While it might be possible to generate templates which behave as required in all defined cases, and invoke UB in all cases which should not be defined, doing so will be useless if not counterproductive unless a compiler is programmed to exploit the UB in question to facilitate such optimizations. Programming a compiler to exploit such optimizations in cases where code uses a Standard-defined type that exists for that purpose is apt to be easier and more effective than trying to identify patterns within user types where it would be exploitable, and would also be less likely to have undesired side-effects.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Alternatively, the compiler's developers could add a `#pragma` directive that allows the user to specify whether a type is intended to implement `restrict` semantics. This would allow an easy manner for either standard or user-defined `restrict`-like types to use the same optimisations, with no additional overhead for parsing UDTs. – Justin Time - Reinstate Monica Jul 02 '16 at 17:44
  • @JustinTime: It might be possible to use a #pragma along with a dummy user type, but if different compilers implement slightly-different semantics it may be difficult to know what promises a particular declaration is intended to make. Further, the effects of `restrict` are temporally-limited, so if e.g. `foo` receives a restrict-qualified pointer and calls `bar`, and then `bar` calls `foo` recursively passing a pointer *that cannot have been received from the first call to `foo`*, the compiler can assume the pointers don't alias... – supercat Jul 02 '16 at 18:02
  • @JustinTime: ...even if the code which passed the pointer to the outer `foo` also placed it somewhere `bar` could find it and pass it to the inner call. Type-based aliasing is a nasty kludge to work around the lack of better tools; making code more complicated in the hopes that an optimizer will somehow manage to render it into something better than what a simpler version would have yielded seems a bit backward compared with having a language provide ways of telling the compiler what one actually needs. – supercat Jul 02 '16 at 18:07
  • Good points, I was just extrapolating from the idea of optimisations aimed specifically at something in the standard, to a way to make those same optimisations available both for standard and user-created code. In this case, for anything defined in the standard, the compiler's devs would put the pragma inside their implementation of the standard implementation, while also documenting the pragma so that users could use it as well. – Justin Time - Reinstate Monica Jul 02 '16 at 18:24
  • 1
    Widely used c++ compilers implement the __restrict extension, often with same effect as c restrict. – tim18 Jul 02 '16 at 19:35
  • @tim18 The problem arises though when you have situations you did not have in `c`, and the differences in compiler implementation of said cases can be enough to upset portability – DarthRubik Jul 02 '16 at 20:31
-1

I think that your solution doesn't fully achieve the intended goal even if the noted UB didn't exist. After all, all real data accesses occur on the built-in type level. If decltype(a->i) is int and your function manipulates int* pointers, then under certain circumstances the compiler should still assume that those pointers could alias a->i.

Example:

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;
}

Usage of noalias will hardly enable optimizing above function to the following:

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

My feeling is that restrict cannot be emulated, and must be supported by the compiler directly.

Leon
  • 31,443
  • 4
  • 72
  • 97
  • 1
    You could create a wrapper class `Int`, then access all `int*`s through `noalias`. So for example, `++*noalias(p); s += *noalias(&a->i)` should work. A raw `int*` will alias all of these though, so you'd have to wrap every `int*` in this way. So it shouldn't be a problem. – PBS Jul 03 '16 at 23:47
  • @PBS So do you agree that wrapping a pointer to a user-defined type in `noalias` isn't enough to make the compiler believe that data directly accessible through that pointer is not aliased and you must wrap other potentially aliasing pointers too? Then an easier solution could be to introduce a function `template void doNotAlias(T* p1, T* p2) { if (p1 == p2) undefined_behaviour(); }` and call it for every pair of potentially aliasing pointers. – Leon Jul 04 '16 at 05:51
  • Yes, but that was never the intent of `noalias` anyway. The stated intent was only to stop data members of different `T*`s from aliasing. So in your example, that `*p` and `a->i` alias is in fact the desired behaviour. Also, about your `doNotAlias` - does that actually work? I'd have thought that just causes undefined behaviour if `p1 == p2`. A better way might be `__builtin_assume(p1 != p2)`, but this isn't portable. – PBS Jul 04 '16 at 09:36
  • I've just noticed that the example in my first comment doesn't quite work: in `int* --> Int* --> noalias`, the first cast isn't allowed. But this can be fixed using `reinterpret_cast` (ugh), resulting in the same UB as noted in the question. – PBS Jul 04 '16 at 09:55
  • @PBS I didn't test `doNotAlias`. I simply tried to exploit the typical handling of UB by mainstream compilers. When encountered with a possibility of UB, they simply assume that paths that would lead to it are not taken. Therefore `doNotAlias(p1, p2)` should be equivalent to `__builtin_assume(p1 != p2)` (but, unfortunately, without any guarantees) – Leon Jul 04 '16 at 09:58
  • About built-in types, you are correct, but for structs (which I would assume is the main thing you would pass by reference) this works beautifully if there is no UB. Also if you wanted ints to work you could just for get inheritance in the hidden class. Just put an align as T somewhere. When you construct you noalias class do the reinterpret_cast again into the sub class. When you return a T* just reinterpret_cast it back. That takes care of a lot issues that exist right now.....as far as I can see. – DarthRubik Jul 04 '16 at 13:09
  • @PBS [Here](https://godbolt.org/g/f2yKPX) is what I am talking about. Check the output....it optimizes away the check (still has UB though) – DarthRubik Jul 04 '16 at 13:59
  • @DarthRubik In your example, in the end the data accesses happen on `T*` anyway, so there is no strict aliasing optimisation going on here - the compiler inlines everything into `main` where it knows that `a` and `b` are the addresses of different variables. Compiling with `-c` would stop this, but that doesn't seem to work with godbolt... – PBS Jul 04 '16 at 16:34
  • @PBS I think it would still work, because the stored pointer is a `Dummy` type. When the compiler sees that it once was from a `Dummy` type, it is free to optimize using strict aliasing rule...now whether this might be too subtle of a hint to a random compiler I don't know. – DarthRubik Jul 04 '16 at 16:40