HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Meta-program to find square numbers

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
findnumbersprogrammetasquare

Problem

I'm new to template meta-programming and wrote some code to determine if a number is a perfect square. For example, in my program, SQUARE::value and SQUARE::value evaluate to true while SQUARE::value is false. I'm open to any suggestions on how I could implement this better, cleaner, or make use of standard library functions. Also, please point out any meta-programming conventions I may have overlooked.

template
struct IF {
   static const bool ret = Then;
};

template
struct IF {
   static const bool ret = Else;
};

template
struct GREATER {
   static const bool ret = v1 > v2;
};

template
struct IS_SQUARE {
   static const bool value = IF::ret,
                   IS_SQUARE::value,
                   false>::ret
                >::ret;
};

template
struct IS_SQUARE {
   static const bool value = false;
};

template
struct SQUARE {
   static const bool value = IS_SQUARE::value;
};

template<>
struct SQUARE {
   static const bool value = true;
};

// example usage
int main() {
   static_assert(SQUARE::value, "must be square");
   static_assert(SQUARE::value, "must be square");
   static_assert(SQUARE::value, "must be square"); // compile error
}

Solution

I'd recommend against using ALL_CAPS for class-names. It's not really in accordance with C++ conventions.

template
struct IS_SQUARE {
   static const bool value = IF::ret,
                   IS_SQUARE::value,
                   false>::ret
                >::ret;
};


You can replace the outer if with || (if foo then true else bar is equivalent to foo || bar) here to get rid of one level of nesting and simplify the condition a bit. One might think that you can also replace the inner if with &&, but that won't work because template-instantiation doesn't short-circuit in the way you'd want it to when using logical operators (i.e. foo && Bar will try to instantiate Bar even if foo is false, leading to infinite recursion in this case).

template
struct SQUARE {
   static const bool value = IS_SQUARE::value;
};


You might want to use 0 instead of 1 as the starting value here. Otherwise you'll get incorrect results for n=0 (which is a square of itself and should thus return true).

template<>
struct SQUARE {
   static const bool value = true;
};


This is redundant because IS_SQUARE (or IS_SQUARE) is true anyway.

Apart from the nitpicks listed above, your code looks fine (or as fine as TMP code ever looks, anyway). However it can be completely replaced using constexpr, which still gives you compile-time evaluation (including the ability to use the result inside template arguments or static_assert), while being greatly more readable:

The beauty of C++11's constexpr feature is that it allows you to write compile-time expressions using normal C++ syntax, instead of encoding everything using template classes. You're not allowed to use loops or any side-effects, so you still have to use the recursive approach you used (as opposed to the iterative approach you'd probably use at run-time), but it can be greatly simplified by expressing it as a simple recursive function instead of a bunch of templates:

constexpr bool is_square(size_t n, size_t r=0) {
    return (r*r == n)  ||  ( (r*r < n) && is_square(n, r+1));
}

int main() {
    static_assert(is_square(1), "must be square");
    static_assert(is_square(4), "must be square");
    static_assert(is_square(5), "must be square"); // compile error
}


As a learning-exercise for template meta programming this version is completely useless, of course, as it does not actually contain any templates. But it's definitely more readable (and more writable as well) and can be used in any scenario where the template-version can be used. So in any real C++11 meta-programming project, I'd always recommend using constexpr over templates when it's possible. This way you can perform your calculations in functions that look like C++ functions (well, C++ functions that are written in a surprisingly functional style) and use templates to define classes (which depend on the results of those calculations).

Code Snippets

template<size_t n, size_t r>
struct IS_SQUARE {
   static const bool value = IF<n == r*r,
                true,
                IF<GREATER<n, r*r>::ret,
                   IS_SQUARE<n, r+1>::value,
                   false>::ret
                >::ret;
};
template<size_t n>
struct SQUARE {
   static const bool value = IS_SQUARE<n, 1>::value;
};
template<>
struct SQUARE<1> {
   static const bool value = true;
};
constexpr bool is_square(size_t n, size_t r=0) {
    return (r*r == n)  ||  ( (r*r < n) && is_square(n, r+1));
}

int main() {
    static_assert(is_square(1), "must be square");
    static_assert(is_square(4), "must be square");
    static_assert(is_square(5), "must be square"); // compile error
}

Context

StackExchange Code Review Q#7128, answer score: 5

Revisions (0)

No revisions yet.