0

Hello guys (and happy new year!)

I'm writing a (not really) simple project in C++ (my first, coming from plain C). I was wondering if there is a way to simplify the definition for multiple functions having the same template pattern. I think an example would be better to explain the problem.

The context

Let's assume I have a "Set" class which represents a list of numbers, defined as

template <class T>
class Set {
    static_assert(std::is_arithmetic<T>(), "Template argument must be an arithmetic type.");
    T *_address;
    ...
}

So if I have an instance (say Set<double>) and an array U array[N], where U is another arithmetic type and N is an integer, I would like to be able to perform some operations such as assigning the values of the array to those of the Set. Thus I create the function template inside the class

template <class U, int N>
void assign(U (&s)[N]) {
    static_assert(std::is_arithmetic<U>(), "Template argument must be an arithmetic type.");
    errlog(E_BAD_ARRAY_SIZE, N == _size);
    idx_t i = 0;
    do {
        _address[i] = value[i];
    } while (++i < size);
}

The problem

As far as my tests went the code above works perfectly fine. However I find it REALLY REALLY ugly to see since I need the static_assert to ensure that only arithmetic types are taken as arguments (parameter U) and I need a way to be sure about the array size (parameter N). Also, I'm not done with the assign function but I need so many other functions such as add, multiply, scalar_product etc. etc.!

The first solution

I was wondering then if there is a prettier way to write this kind of class. After some work I've come up with a preprocessor directive:

#define arithmetic_v(_U_, _N_, _DECL_, ...)                                             \
        template <class U, idx_t N> _DECL_                                              \  
        {                                                                               \
            static_assert(std::is_arithmetic<U>(),"Rvalue is not an arithmetic type."); \
            errlog(E_BAD_ARRAY_SIZE, N == _size);                                       \
            __VA_ARGS__                                                                 \
        }

thus defining my function as

arithmetic_v(U, N,
             void assign(U (&value)[N]),
                 idx_t i = 0;
                 do {
                     _address[i] = value[i];
                 } while (++i < _size);
             )

This is somehow cleaner but still isn't the best since I'm forced to lose the brackets wrapping the function's body (having to include the static_assert INSIDE the function itself for the template parameter U to be in scope).

The question

The solution I've found seems to work pretty well and the code is much more readable than before, but... Can't I use another construct allowing me to build an even cleaner definition of all the functions and still preserving the static_assert piece and the info about the array size? It would be really ugly to repeat the template code once for each function I need...

The thanks

I'm just trying to learn about the language, thus ANY additional information about this argument will be really appreciated. I've searched as much as I could stand but couldn't find anything (maybe I just couldn't think of the appropriate keywords to ask Google in order to find something relevant). Thanks in advance for your help, and happy new year to you all!

Gianluca

gianluca
  • 337
  • 1
  • 3
  • 15
  • 1
    This question is better suited for [CodeReview](http://codereview.stackexchange.com). – David G Jan 03 '14 at 01:51
  • `#define` big no no :E – deW1 Jan 03 '14 at 01:52
  • why test size at run time? and why do you need to test `U`? – Yakk - Adam Nevraumont Jan 03 '14 at 02:09
  • Why define your own? Use a `std::set`. – Yuushi Jan 03 '14 at 02:25
  • @Yakk the size is not ALWAYS tested at runtime (it only is when compiled with certain words defined). errlog is a preprocessor macro which contains a sort of `static_assert` in that case but in some cases the array passed as an argument may only be built at runtime. I need to test `U` because performing a sum between a double and a string is not exactly what I was thinking. I don't want sets of strings to be created at all! If you know any better method to test for a template argument to be of an arithmetic type I will gladly listen. – gianluca Jan 03 '14 at 03:08
  • @Yuushi std::set is not what I'm looking for. I need very few of its properties and I need many more that aren't there, so I'm writing my own class (this also serves as a good exercise). Also, I need a VERY specific way to organize the allocated memory (I didn't include it in the text because that's not what the question is about) and be able to dump it to a binary file for later reading with other software and I'm not sure std::set does the job. Furthermore if I need to use the `Set` class to represent a particular subset of the original set the iterator logic is not what I need. – gianluca Jan 03 '14 at 03:10
  • It will *already* fail to compile when you try to do a sum between a `double` and a `std::string`, so that is actually not a good reason. There are good reasons: do you have one? – Yakk - Adam Nevraumont Jan 03 '14 at 03:11
  • @yakk Ok, wrong example. What about performing a sum between two `std::string`? The compiler would allow the + operator to be used but it will never have the meaning the class was built for (which is creating a structure in memory capable of storing vectors, sets of vectors as well as a linear space set by subclassing in an appropriate way). There are better classes which deal with strings so I want them to be used instead of mine in case the user really needs it. – gianluca Jan 03 '14 at 14:37
  • @gianluca `static_assert` in the container, not the algorithms? Or trust your users? – Yakk - Adam Nevraumont Jan 03 '14 at 15:25
  • @Yakk Which container? The `U` template parameter is only defined inside those functions since it can be different from the class template parameter `T` (I want to be able to assign a C array of `int`s to a `Set` of `float`s), so I have no container seeing `U`. Are you suggesting wrapping all the functions in a sub-container? How can I do that? – gianluca Jan 03 '14 at 15:51
  • @gianluca no -- if your container contains `int`s, anything that can `+` with `int` should be fine to `+` with `int`. Either the end user is doing something funny (in which case, they get what they want), or they are not. The `static_assert` might be useful for better error messages, but duck typing should be quite fine for most uses, unless you intend to overload it. – Yakk - Adam Nevraumont Jan 03 '14 at 16:16

2 Answers2

6

I strongly suggest against using macros, unless there is no way around them (a case I could think of is getting the line number for debugging purposes). From the Google C++ Style Guide (http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preprocessor_Macros):

Macros mean that the code you see is not the same as the code the compiler sees. This can introduce unexpected behavior, especially since macros have global scope.

I really don't see why you consider using a static_assert ugly. There is another way to ensure that a template is specialized only for some types using SFINAE.

template <class T, class Enable = void>
class X;

template <class T>
class X<T, typename std::enable_if<std::is_integral<T>::value>::type> {
};

and you could do this even prettier using a using statement (no pun intended):

template <class T>
using enable_if_integral_t = typename std::enable_if<std::is_integral<T>::value>::type;

template <class T, class Enable = void>
class X;

template <class T>
class X<T, enable_if_integral_t<T>> {
};

And now

X<int> x; // ok, int is integral
X<float> y; // compile error

SFINAE (Substitution failure is not an error) is a feature in C++ in which you don't get an error if a template specialization fails.

template <bool Cond, class T = void> struct enable_if. The type T is enabled as member type enable_if::type if Cond is true. Otherwise, enable_if::type is not defined. So for a float type is_integral is false and enable_if::type doesn't exist, so the template specialization

template <class T>
class X<T, typename std::enable_if<std::is_integral<T>::value>::type>

fails, but the generic template is used instead

template <class T, class Enable = void>
class X;

which is declared, but not defined.

This is useful as you can have more specializations like:

template <class T>
using enable_if_integral_t = typename std::enable_if<std::is_integral<T>::value>::type;

template <class T>
using enable_if_floating_t = typename std::enable_if<std::is_floating_point<T>::value>::type;


template <class T, class Enable = void>
class X;

template <class T>
class X<T, enable_if_integral_t<T>> {
};
template <class T>
class X<T, enable_if_floating_t<T>> {
};

Hope you find this at least interesting.

Happy new year!

Edit

Where should I put the <T, enable_if_integral_t<T>> in a function definition? I can only get this done with class templates...

For a function, the enable_if::type can be the return type. For example if f returns int, you can have:

#include <type_traits>

template <class T>
typename std::enable_if<std::is_integral<T>::value, int>::type f(T a) {
    return 2 * a;
}

int main() {
    f(3); // OK
    f(3.4); // error
    return 0;
}

and with using:

#include <type_traits>

template <class T, class Return = void>
using enable_if_integral_t = typename std::enable_if<std::is_integral<T>::value, Return>::type;

template <class T>
enable_if_integral_t<T, int> f(T a) {
    return 2 * a;
}

int main() {

    f(3); // OK
    f(3.4); // Error
    return 0;
}
bolov
  • 72,283
  • 15
  • 145
  • 224
  • Thank you! I did try this approach before turning to the one I posted but I never managed to get it working. However my code was sligtly different, so I will try again and let you know! – gianluca Jan 03 '14 at 14:38
  • Tried but could not get it working! Where should I put the `>` in a **function** definition? I can only get this done with class templates... – gianluca Jan 03 '14 at 14:59
1

I don't understand why you consider the static_assert or errlog statements so ugly, and suspect it's partly unfamiliarity with the language. Still, you could easily write a function or macro (if you want to use __LINE__ inside the assign etc. function), to move them out of line, allowing usage like:

template <class U, int N>
void assign(U (&s)[N]) {
    assert_array_n_numbers(s);
    idx_t i = 0;
    do {
        _address[i] = s[i];
    } while (++i < size);
}

Can't I use another construct allowing me to build an even cleaner definition of all the functions and still preserving the static_assert piece and the info about the array size? It would be really ugly to repeat the template code once for each function I need...

In terms of what's possible - though IMHO likely undesirable obfuscation - you could have your functions accept (an) argument(s) that has a templated implicit constructor from an array, verifying it's arithmetic in that constructor then verifying size in the function using it, allowing usage like:

template <typename U>
void assign(Arithmetic_Array<U>& s) {
    assert_same_size(s);
    idx_t i = 0;
    do {
        _address[i] = s[i];
    } while (++i < size);
}

Implementation:

template <typename T>
class Arithmetic_Array
{
  public:
    template <size_t N>
    Arithmetic_Array(T (&a)[N])
      : p_(&a), size_(N)
    {
        static_assert(std::is_arithmetic<T>(),"Rvalue is not an arithmetic type.");
    }

    T& operator[](size_t i) { return p_[i]; }
    const T& operator[](size_t i) const { return p_[i]; }

    size_t size() const { return size_; }

  private:
    T* p_;
    size_t size_;
};

Discussion

"cleaner" can be subjective. In particular, you should consider the value of "normal" non-macro using-the-intuitive-type C++ source as documentation and for maintainability. If a macro substantially simplifies many functions - and particularly if it's only used in an implementation file and not a shared header - then it's worthwhile, but if there's only marginal benefit it's not worth the obfuscation and de-localisation. All that template stuff might seem convoluted and ugly when you're new to the language, but after a while it's understood at a glance and helps readers understand what the function goes on to do.

It's also common in C++ to embrace a "duck typing" attitude to template's parametric polymorphism. That means that you can let people pass in arguments of whatever type, and if those types support the operations that the template implementation attempts on them (i.e. compile), then hopefully it'll be what the caller wants. That's one reason that it's a good idea to create types that have predictable semantic behaviour, for example - only using operator overloading when the affect is similar to the same operators on built in types or std::string.

The stricter enforcement you'd like has its place though - Bjarne Stroustrup and others have spent a lot of time working on "Concepts" which are a mechanism for enforcing expectations on types used as template parameters, and would have been a good fit for your "arithmetic types" stipulation here. I hope they'll make it into the next C++ Standard. Meanwhile, static assertions are a good way to go.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • Thank you for the answer! However the whole point of creating such a function is to be able to assign a standard C array to my `Set` internal array so creating another wrapping class would not do the job... I need to be able to do something like `Set s1(3);` followed by `s1 = (double[3]) {1, 2, 3};` Is this possible with your approach? – gianluca Jan 03 '14 at 14:42
  • `but after a while it's understood at a glance and helps readers understand what the function goes on to do.` Got it. I'm removing the macro and putting everything inline, your statement makes a lot of sense. – gianluca Jan 03 '14 at 15:01