4
\$\begingroup\$

Template IO Module

This is a single-file, header-only input validation with two variadic output functions. One output function simply inserts a new line char after every output and the other one inserts a space; this is the only difference.

The end goal of this project is a convenient, cross-platform IO module, and I thought that the static type-checking and compile-time generation of templates would be the perfect combination of type safety without run-time overhead, with the added bonus of the simplicity of adding a header-only module to an existing project.

Source

#ifndef _INPUT_VALIDATION_HPP
#define _INPUT_VALIDATION_HPP

/** Required STL Header Files for this module:
*  #include <iostream>
*  #include <limits>
*  #include <typeinfo>
*
*/


namespace IO {

    template <typename T>
    void print(const T& input)
    {
        std::cout << input << ' ';
    }


    template <typename T, typename... Types>
    void print(const T& firstArg, const Types&... arguments)
    {
        std::cout << firstArg << ' ';
        print(arguments...);
    }


    template <typename T>
    void println(const T& input)
    {
        std::cout << input << std::endl;
    }


    template <typename T, typename... Types>
    void println(const T& firstArg, const Types&... arguments)
    {
        std::cout << firstArg << std::endl;
        println(arguments...);
    }


    template <typename T>
    T get()
    {
        auto IO_status_flag = false;

        T response;
        std::cin >> response;

        do {
            if (std::cin.fail()) {

                IO::print("[Error] Please enter a valid response:");
                std::cin.clear();
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

                std::cin >> response;
            } else {
                IO_status_flag = true;
            }
        } while (IO_status_flag != true);

        return response;
    }

}


#endif /* _INPUT_VALIDATION_HPP */

Test Code

#include <iostream>
#include <limits>
#include <typeinfo>

#include "input-validation.hpp"


int main()
{
    IO::print("Please enter username:");
    auto username = IO::get<std::string>();

    IO::print("Please enter age:");
    auto age = IO::get<int>();

    IO::print("\nName:", username);
    IO::print("\nAge:", age);

    auto array = { "one", "two", "three", "four", "five", "six" };

    IO::println("\n");

    for(auto str : array)
    {
        IO::println(str);
    }

}

Output

Please enter username: Jose
Please enter age: 23

Name: Jose
Age: 23

one
two
three
four
five
six
\$\endgroup\$
6
\$\begingroup\$

Comment

First question is why I would use this instead of the standard operator<< that we are all very familiar with?

Code Review

Reserved Identifiers

Identifiers with a prefix underscore and an initial capital letter are reserved in all contexts for the implementations. i.e. You are not allowed to use them.

So _INPUT_VALIDATION_HPP is an illegal identifier (for you).

The rules on leading underscore are complex. Even if you know the rules, most people don't, so avoid at all costs.

If you must, then make it a trailing underscore.

Manipulator support.

You have not provided support for stream manipulators (std::hex etc..).

std::endl or '\n'

Prefer '\n' over std::endl;. The difference is that std::endl also forces a flush. Manually forcing a flush is 99.99% of the time incorrect as the stream will automatically flush at the optimal times; doing it manually like this will severely degrade performance.

Recursive over iterative templates.

You use a recursive template design. Personally I prefer the iterative approach as it is more compact (and in my opinion readable).

// You recursive approach.
template <typename T>
void print(const T& input)
{
    std::cout << input << ' ';
}


template <typename T, typename... Types>
void print(const T& firstArg, const Types&... arguments)
{
    std::cout << firstArg << ' ';
    print(arguments...);
}

The alternative:

// The iterative approach.
template <typename T, typename... Types>
void print(Types const&... arguments)
{
    auto unused = {0, (std::cout << arguments << ' ', 0)...};
}

Get only half-checks.

In the get() function if it fails you drop the rest of the line and try again. This implies that the input is line based. BUT on success you don't check that the input is a whole line; you just check the line prefix.

// SO my code is.
int val = IO::get<int>();


// I type
10Ton

Then the above function accepts the input as good. But in my opinion that input should be considered to be incorrect as Ton is being ignored.

\$\endgroup\$
3
\$\begingroup\$

headers must include what they need

This in the header:

/** Required STL Header Files for this module:
*  #include <iostream>
*  #include <limits>
*  #include <typeinfo>
*
*/

and this in the including file:

#include <iostream>
#include <limits>
#include <typeinfo>

#include "input-validation.hpp"

is absolutely not ok! If your header needs some other (standard or non standard) header, then include it. Same for the implementation files: include all headers from which you reference something. Including the same header multiple times is not an issue if all headers have include guards.

Call functions with their qualified name to avoid ADL surprises

namespace documentation {
    struct Page {
        char const * text;
    };

    // Print at printer, costs hundreds of dollars because it's an awesome printer
    void print(Page const &) {
        throw std::logic_error{"WTF! Who turned on the printer!?!"};
    }

    // Write on screen
    std::ostream & operator<<(std::ostream & s, Page const & p) {
        return (s << p.text);
    }
}

int main() {
    try {
        documentation::Page p {"Some nice text"};
        IO::print("Introduction: ", p);
    } catch (std::exception const & e) {
        std::cout << "ERROR: " << e.what() << std::endl;
    }
}

This will be costly ... thus you get yelled at. To avoid such surprises, use the qualified function name when you are sure of the function you want to call:

template <typename T, typename... Types>
void print(const T& firstArg, const Types&... arguments)
{
    std::cout << firstArg << ' ';
    IO::print(arguments...);
//  ^^^^
}

(Though in this case better use the iterative version as shown by Loki)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.