Template Metaprogramming

Among computer languages, C++ often gets described as having low-level functionalities while using high-level (somewhat, at least when compared to C) computer-linguistic constructs. In what follows, please refer to the following example as a preface,


template <typename __tp>
struct __slow {
public:
    using rx_type = __tp;
public:
    inline static constexpr rx_type fox() noexcept(false) {
        return rx_type();
    }
};

This can be used as following,


int main() {
    std::vector<int> some_vector;
    for(int i=1; i<10; ++i) {
        // one way to store all the even numbers is
        if(not (i & 1)) {
            some_vector.push_back(i);
        }
        // and the other, using the contrivance defined above
        (i & 1) ? __slow<void>::fox() : some_vector.push_back(i);
    }
    return 0;
}

The utility of this trick is somewhat of a clutter-remover, while writing algorithms mired with too many if-else conditions. It has a simple mechanism, which utilizes the equality of the return-type of both of the functions of ternary conditional operator, as the return type of method std::vector<T>::push_back() is void, which the function __slow<void>::fox() matches. The presented contraption is useful only when the modifying/writing operations are to be executed for only one branch. Of course, it could easily be modified to perform for the otherwise cases. However, be that as it may, for an isolated demonstration; this is not terribly impressive.

Python's inbuilt print function is very impressive. It is so good that it almost never gets noticed. It can print anything, iterable or non-iterable, objects, types and whatnot; it never fails, argument-wise. A long time ago, when I was exploring the template-metaprogramming, I thought it would be good idea to construct such a utility in C++ which would mimic the versatility of Python's print function, using template-metaprogramming. Even though I knew at that time (as I know today) such an enterprise, is beyond my mental-capacity; since, I know that I'm not good enough to internalize the fundamentals regarding empirical-superimposed sciences; that is, it does not come naturally to me. The procedure seems synthetic at times, which defies the analytical-anticipatory-flow by taking short-cuts to working solutions, most of the time. However, the abstraction of template-metaprogramming somewhat negates this, thus I think I've developed somewhat of a "feel" for it. Nevertheless I'm far from being canonical about it, therefore I would like to apologize in advance in case any computer programmer is reading this, for botching the sacred traditions/principles of library-writing and/or programming in general.

OBJECTIVE: However pertaining to the aforementioned wish, let's try to construct a simple print function, which could print objects of different types, for example, it should be able to execute print("a", 5, 5.5, 5UL, 5LL, "this is a string").  

There are a number of ways to achieve this. One of them could be as the following.


template <typename __ostream_type>
struct print {
public:
    using streaming_type = __ostream_type;
public:
    template <typename __tp>
    inline static constexpr int iprint(streaming_type &os_stream, __tp const &tp) {
        os_stream << tp << '\n';
        return 1;
    }
    
    template <typename __tp, typename ... __tp_args>
    inline static constexpr int iprint(streaming_type &os_stream, __tp const &tp, __tp_args const& ... args) {
        os_stream << tp << '\n';
        return iprint(os_stream, args...);
    }
};

Used as print<std::basic_ostream<char>>::iprint(std::cout, 1, 2, "one", 5.5f, 55UL); this does the job, however as I see, there exists a problem with this. Notwithstanding the trivial issue of unwieldy interface which leaves a lot of pointless configuration to the client (a simple wrapper would be able to ameliorate this); the primary concern regarding this code is that it fails to print iterable containers, e.g., print(some_vector) fails where some_vector may be an iterable like std::vector<int> const some_vector = {1,2,3}. Let's fix this issue along with the aforementioned inconvenient interface. Utilizing the SFINAE (substitution failure is not an error) feature of template metaprogramming by using std::void_t; which was introduced by Walter E. Brown and demonstrated here in his amazing (now legendary) presentation in two parts, viz., part one and part two ([Maybe] this one deals with SFINAE and likewise), we may as well have,


template <typename __ostream_type>
struct print {
public:
    using streaming_type = __ostream_type;
public:
    template <typename __tp, typename = void>
    struct printer {
    public:
        inline static constexpr int iprint(bool const &new_line, streaming_type &os_stream, __tp const &tp) {
            (new_line) ? os_stream << tp << '\0' : os_stream << tp << ' ';
            return 1;
        }
    };
    template <typename __tp>
    struct printer<__tp, std::void_t<typename __tp::const_iterator>> {
    public:
        inline static constexpr int iprint(bool const &new_line, streaming_type &os_stream, __tp const &tp) {
            using iterator_value_type = typename __tp::const_iterator::value_type;
            typename __tp::const_iterator _begin = tp.begin();
            typename __tp::const_iterator _end   = tp.end();
            while(_begin != _end) {
                _begin != _end-1 ?
                printer<iterator_value_type>::iprint(false, os_stream, *_begin) :
                printer<iterator_value_type>::iprint(true, os_stream, *_begin);
                ++_begin;
            }
            return 1;
        }
    };
public: // a wrapper to tie all this
    template <typename __tp>
    inline constexpr static int wrapprint(streaming_type &os_stream, __tp const &tp) {
        return printer<__tp>::iprint(true, os_stream, tp);
    }
    template <typename __tp, typename ... __tp_args>
    inline constexpr static int wrapprint(streaming_type &os_stream, __tp const &tp, __tp_args const& ...args) {
        return printer<__tp>::iprint(false, os_stream, tp) + wrapprint(os_stream, args...);
    }
};

along with a simple client side interface, viz.,


template<typename ... __tp_args>
inline constexpr int println(__tp_args const& ... args) {
    using printing = print<std::basic_ostream<char>>;
    return printing::wrapprint(std::cout, args...) + printing::wrapprint(std::cout, '\n');
}
inline constexpr int println() { return println('\0'); }

and we're done. This works for all iterables which have a const_iterator. A working example with all of its nuts and bolts is given at https://gcc.godbolt.org/z/s9oMPq8xz, mimicking Python's square brackets for iterables. All of the code given in this blog has been assembled and run here: https://gcc.godbolt.org/z/cqdcoornr. At the time of writing this, I was quite involved with languages like Scala and Chapel. Thus the names of the printing functions of those languages, have been lifted and used in the working example. As far as the final code is concerned, I guess it's not all that terribly impressive, since much finer methods exist to achieve more functionality than this; nevertheless, as haphazard as this is, it does demonstrate the implementation details of std::void_t for SFINAE. Which, as I recall, I really wanted to see in action. I really want to talk more about the logic behind templates and what exactly was it which caught my attention. Especially about their utility in type_traits. May be next time.

Comments

Popular posts from this blog

The Age of Idiot

The Immovable Might of Indian Bureaucracy