4

In a bunch of code that I'm writing I want to indicate that certain variables are to be used in a certain way, or have a certain characteristic to them. For the sake of discussion, suppose variables can be sweet, salty, sour or bitter.

What I use now is something like:

int foo() {
    int salty_x;
    int sour_y;
    do_stuff_with(salty_x,sour_y);
}

And I might also have sour_x, or salty_y etc.

Ideally - but this is not valid C++ - I would have been able to write something like this:

int foo() {
    namespace salty { int x; }
    namespace sour { int y; }
    do_stuff_with(salty::x,sour::y);
}

and this would nicely allow for a "sour x" and a "salty x" in the same function - if this syntax had been valid of course.

This may remind you of Hungarian Notation, except that it's not about variable types or sizes, and that the saltiness or sourness etc. are not inherent in x or in y - they only describe the way they're used.

Now, you could ask: "Ok, why not just put these in struct's?", that is, why not do:

int foo() {
    struct { int x; } salty;
    struct { int y; } sour;
    do_stuff_with(salty.x,sour.y);
}

But that preculdes defining additional salty/sour variables; and if I bunch them all at the beginning of the function, C-style, then it looks as though I indicate the variables are related, which is not necessarily the case.

What I currently do is just prefix the names: salty_x, sour_y. It's ugly but it works.

My question: Is there something else I could do which would look closer, in use, to my desired code, and at the same time not require too much "coding gymnastics"?


Due to popular demand: As a motivating/concretizing example, "salty" might mean "is uniform across all threads in a GPU warp, but possibly not across different warps" and "sour" might mean "is uniform across all threads in a CUDA kernel grid block / OpenCL workgroup when they reach this point in the code". But this is not a question about GPUs, CUDA, or OpenCL.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 1
    @Rakete1111 With the struct method, if you want to define a salty x somewhere and a salty y elsewhere but in the same scope, you would have conflicting declarations for the variable salty. – Nelfeal Jan 27 '18 at 16:57
  • I don't think there are any difference between `sour::x` and `sour_x` in a local scope. Using namespace globally just because the name structure is complex and there are many names. In local scope, it's not the case. – llllllllll Jan 27 '18 at 16:57
  • 4
    I'm inclined to say "just use prefixes". I would need a concrete example of why you think it's ugly to try and find another solution. – Nelfeal Jan 27 '18 at 16:58
  • 2
    You say "In a bunch of code that I'm writing I want to indicate that certain variables are to be used in a certain way, or have a certain characteristic to them." *This is the definition of [type](https://en.wikipedia.org/wiki/Data_type)*: a set of variable that share some characteristic that can only be used in in a certain way (way=method). – Oliv Jan 27 '18 at 17:16
  • Being less abstract may help here. It is likely your abstraction of your problem is imperfect, so a concrete model will help. – Yakk - Adam Nevraumont Jan 27 '18 at 17:24
  • 2
    Change your definition of ugliness. `salty_x` and `sour_y` look beautiful in my eyes when I'm coding in Python. In C++ I prefer `saltyX` and `sourY`. As long as they're in harmony with other variable names (all are snake_case or all are CamelCase) they look beautiful, ugliness comes when you break the harmony. – Sassan Jan 27 '18 at 18:01
  • @Oliv: But that won't help me. I mean, suppose I had a `using salty = T`; definition, and then `salty x;` That's no good! I still don't notice the saltiness in `do_things_with(x)`. So, the type is not the issue. – einpoklum Jan 28 '18 at 00:03
  • @Yakk: You're right, I guess, but I'm afraid making this concrete will derail the discussion.... oh, ok. See edited. – einpoklum Jan 28 '18 at 00:04
  • @Nelfeal: See edit, but I really don't want this question to be about GPU kernel coding. – einpoklum Jan 28 '18 at 00:08
  • @einpoklum your are not defining a type but an alias. A type: `class salty{...}` then inside your class implement the "way" your variable should be used. – Oliv Jan 28 '18 at 08:34
  • @Sassan: Interesting point. Indeed, so far I haven't found something less ugly... – einpoklum Jan 28 '18 at 10:23

4 Answers4

1

The hardest constraint was

Sometimes I even want to have a "sour x" and a "salty x" in the same function

So - the solution is the first usage of variadic template template parameter I ever made - so, here you are:

template <typename T>
struct salty
{
    T salty;
};
template <typename T>
struct sour
{
    T sour;
};

template <typename T, template <typename> class  ...D>
struct variable : D<T>...
{};

And the usage:

salty<int> x;
x.salty = 5;

variable<int, salty, sour> y;
y.sour = 6;
y.salty = 5;
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • the usage looks like `x` and `y` are structures, not variables, and that `x` has a salty field and a sour field - and that's not what I want to convey with my code. But thanks for the effort. – einpoklum Jan 28 '18 at 00:00
1

I'm sure you've checked all conventional approaches and neither was satisfactory... Lets turn to the magic then to achieve (I think) exactly what you want (c++17 will be needed):

#include <iostream>
#include <type_traits>
#include <variant>
#include <utility>
#include <typeinfo>
#include <typeindex>
#include <map>

template <auto Label>
using ic = std::integral_constant<decltype(Label), Label>;

template <class... Ts>
struct context {
    template <auto Label, auto (*Namespace)(std::integral_constant<decltype(Label), Label>)>
    decltype(Namespace(ic<Label>{}))& get() {
        try {
            return std::get<decltype(Namespace(std::integral_constant<decltype(Label), Label>{}))>(values[typeid(std::pair<std::integral_constant<decltype(Namespace), Namespace>, std::integral_constant<decltype(Label), Label>>)]);
        } catch (std::bad_variant_access&) {
            values[typeid(std::pair<std::integral_constant<decltype(Namespace), Namespace>, std::integral_constant<decltype(Label), Label>>)] = decltype(Namespace(std::integral_constant<decltype(Label), Label>{})){};
        }
        return std::get<decltype(Namespace(std::integral_constant<decltype(Label), Label>{}))>(values[typeid(std::pair<std::integral_constant<decltype(Namespace), Namespace>, std::integral_constant<decltype(Label), Label>>)]);
    }
    std::map<std::type_index, std::variant<Ts...>> values;
};

int main(){
    enum { x }; // defining label x
    int salty(ic<x>); // x is an int in salty namespace 
    enum { y }; // defining label y
    float sour(ic<y>); // y is a float in sour namespace
    context<int, float, char> c;


    c.get<x, salty>() = 2;
    c.get<y, sour>() = 3.0f;

    char sour(ic<x>); // x is a char in sour namespace 

    c.get<x, sour>() = 'a';

    std::cout << "salty::x = " << c.get<x, salty>() << std::endl;
    std::cout << "sour::y = " << c.get<y, sour>() << std::endl;
    std::cout << "sour::x = " << c.get<x, sour>() << std::endl;
}

One thing to be mentioned - gcc doesn't like the code though according to standard it should: [see this] [and that].

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • +1 for effort. This is, umm, not so bad, I guess. It doesn't beat the prefixing in terms of readability though. Have you thought about a templated `operator()` for the context instead of `get()`? Perhaps that would allow for something like `c()` instead of `c.get()`. – einpoklum Feb 01 '18 at 10:59
  • @einpoklum I think since c++17 it might actually work. Let me see. – W.F. Feb 01 '18 at 11:00
  • @einpoklum Scratch that. It still doesn't and it's actually rational as it would see c as a variable template which can't be declared in non global (static) context... – W.F. Feb 01 '18 at 11:03
  • @einpoklum this one has more readable syntax (I think best one you can actually get) `ns->*label` :) drawback - it uses macros – W.F. Feb 03 '18 at 10:11
  • Thank you... I can't double-plus-1 you though. – einpoklum Feb 03 '18 at 10:19
  • @einpoklum Not doing this for points just to release language bounderies ;) – W.F. Feb 03 '18 at 10:21
  • @einpoklum Unfortunately proposed 2-nd solution might be [ill-formed with no diagnostic required](https://stackoverflow.com/questions/48586355/why-doesnt-function-declared-inside-other-function-participate-in-argument-depe)... Not sure if one can do it in another way... cross my fingers though! – W.F. Feb 05 '18 at 10:56
0

If I understand your edit correctly, you want to be able to define variables that behave exactly like int or float variables, but retain additional, ideally compile-time, information about their types.

The only thing I can't help you with is this:

Sometimes I even want to have a "sour x" and a "salty x" in the same function, which I could do if this syntax had been valid.

Personally, I would just prefix the variable name.

Anyway, here is an example of what you can do.

enum class Flavor {
    Salty, Sour
};

template <typename T, Flavor f>
struct Flavored {
    using Type = T;
    static constexpr Flavor flavor = f;

    T value;

    Flavored(T v) : value(v) {}
    operator T() { return value; }
};

And here is an example of how to use it.

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
  • No, you misunderstand me. The additional information needs to be _visible to the programmer_ when referring to a variable, not in that variable's type. – einpoklum Jan 28 '18 at 10:21
  • The only things visible to the programmer when refering to a variable is its identifier, and possibly enclosing namespaces, like a true namespace or a struct. Since none of these things solve your problem, you are left with the identifier, by using prefixes. There is one other way, but I would consider it way uglier: making a map-like structure from strings to variables ([`std::any`](http://en.cppreference.com/w/cpp/utility/any)). You can then define maps named "salty" and "sour", and add a variable to them using the string key as identifier. But really, you should stick with prefixes. – Nelfeal Jan 28 '18 at 11:11
0

Postfixes.
They usually hamper readibility a lot less than prefixes, while maintaining the same hinting quality.

Yes I know it's obvious, but it's very possible that it hadn't come to your mind.

And mind that the Hungarian Notation was originally meant to be employed pretty much as you would in your case; see "Apps Hungarian" in that wikipedia entry.

gbr
  • 1,264
  • 8
  • 27
  • Well, it's a fair suggestion, but I don't feel it helps readability that much. So no up or downvote from me... – einpoklum Oct 05 '18 at 14:08
  • No worries, I just recommend you to try it for some time when you have the occasion, if you haven't yet. I thought I had read some study about this, but I can't find it right now (and I'm not sure it exists.). – gbr Oct 05 '18 at 15:52
  • It has to do with the fact that you encounter the important information first, and you can skip the rest easily if you already know what it is. With prefixes (e.g. in most Hungarian), it's true that you learn to skip them automatically if you deal with them all the time, but there is always some mental work involved. With postfixes/suffixes instead, you only have to skip (or read) them if there's more code on the line after the variable, but in a large amount of cases there isn't or you don't care about it, so it's usually, on the whole, a lot less work. – gbr Oct 05 '18 at 15:52