Home > Software design >  Can a variable receive many types of data?
Can a variable receive many types of data?

Time:10-19

Can you create a variable which enables the user to put any type of data like int, string ... and then the program prints a message which tells the user the type of data they entered?

CodePudding user response:

It's possible to deduce the type of input for example:

#include <initializer_list>

int main()
{
    // std::initializer_list<int>
    auto A = { 1, 2 };

    // std::initializer_list<int>
    auto B = { 3 };

    // int
    auto C{ 4 };

    // C3535: cannot deduce type for 'auto' from initializer list'
    auto D = { 5, 6.7 };

    // C3518 in a direct-list-initialization context the type for 'auto'
    // can only be deduced from a single initializer expression
    auto E{ 8, 9 };

    return 0;
}

It's also possible to answer what type of data has been input:

enter image description here

#include<iostream>
#include<string.h>
using namespace std;

int isint(char a[])
{
    int len=strlen(a);
    int minus=0;
    int dsum=0;
    for(int i=0;i<len;i  )
    {
        if(isdigit(a[i])!=0)
            dsum  ;
        else if(a[i]=='-')
            minus  ;        
    }
    if(dsum minus==len)
        return 1;
    else 
        return 0;
}
int isfloat(char a[])
{
    int len=strlen(a);
    int dsum=0;
    int dot=0;
    int minus=0;
    for(int i=0;i<len;i  )
    {
        if(isdigit(a[i])!=0)
        {
            dsum  ;
        }
        else if(a[i]=='.')
        {
            dot  ;
        }
        else if(a[i]=='-')
        {
            minus  ;
        }       
    }
    if(dsum dot minus==len)
        return 1;
    else
        return 0;
}
int main()
{
    char a[100];
    cin>>a; 
    
    if(isint(a)==1)
    {
        cout<<"This input is of type Integer";
    }
    else if(isfloat(a)==1)
    {
        cout<<"This input is of type Float";
    }
    else
    {
        cout<<"This input is of type String";
    }
    
}

CodePudding user response:

Not really.

When taking input from std::cin with a stream input function, the compiler always has to know the type of the input.

e.g.:

int a = 0;
std::cin >> a;

This effectively calls a function with a signature like this:

std::istream& operator>>(std::istream& stream, int& a);

That function interprets the characters on the stream as an integer, and attempts to convert those characters to an int value. (Which may succeed, or fail if the input doesn't actually represent an integer).

We can write our own functions like the above to convert from characters on the input stream to our own custom classes. However, the type must always be known at compile time.


Of course, we could simply read that sequence of characters as a string (i.e. a sequence of characters).

We could then write our own logic to identify and convert the type of value contained in the string. (e.g. If it's contained in quotes, it's a string. If it's only numbers it's an integer. If it's numbers and a decimal, it's a float). Note that this could get very complicated, and it's hard to be unambiguous (e.g. 12 could be interpreted as an int, a float or a double, or some custom type depending on what rules we choose).

As an example, here are the rules about how C interprets integer literals in code.

CodePudding user response:

Can a variable receive many types of data?

Sort of. A std::variant can hold any one of a number of predetermined types, like std::variant<int, double, float, Foo>. The only problem is to figure out what type to put in it from the input a user gives.

One approach could be to read a line into a std::string and try to extract the data from that string into all of the types in the variant until one succeeds.

To do this, we can put the string in a std::istringstream (which is similar to std::cin but only contains the data you put in it) and then extract from that istringstream into a variable of each type in the variant. As soon as one succeeds to deplete the istringstream completely, we can call that a match.

If none of the types matches, we don't want to return anything that can be mistaken for a real value so we can put the variant in a std::optional - which either holds a value or don't.

To help with the extraction from a std::string I've made a function template called extract who's template parameters are the types it should try to extract from the string. It'll return a std::optional<std::variant<TheTypes...>> with the result.

namespace detail {
// a helper function that will try to extract into one type at a time recursively:
template<size_t I, class... Ts>
std::optional<std::variant<Ts...>> extract_helper(const std::string& str) {
    // the type to try to extract:
    using value_type = std::tuple_element_t<I, std::tuple<Ts...>>;

    std::istringstream is(str);     // put the string in an istringstream
    value_type var;                 // declare a varible of the current type

    // try to extract into var and check that the istringstream is depleted:
    if(is >> var && !is.rdbuf()->in_avail()) return {std::move(var)};

    // it wasn't value_type, try next if we've got more types to try:
    if constexpr(I < sizeof...(Ts) - 1) return extract_helper<I 1, Ts...>(str);

    return {};     // no type succeeded - return empty optional
}
} // namespace detail

// this the the function the user will use:
template<class... Ts>
std::optional<std::variant<Ts...>> extract(const std::string& str) {
    return detail::extract_helper<0, Ts...>(str);
}

With this you can let the user input something and check against the types you supply. You can use std::visit that applies a callable to the variant where the correct type has been resolved.

struct Line {  // A std::string wrapper to extract a whole line
    std::string str;

    friend std::istream& operator>>(std::istream& is, Line& l) {
        return std::getline(is, l.str);
    }
    friend std::ostream& operator<<(std::ostream& os, const Line& l) {
        return os << l.str;
    }
};

// helper type for visitor
template<class... Ts>
struct overloaded : Ts... {
    using Ts::operator()...;
};

int main() {
    for(std::string line; std::getline(std::cin, line);) {
        // use the above `extract` function and try to match a few types:
        auto opt = extract<long long, long double, std::string, Line>(line);

        if(opt) {             // if this is true, we got a match
            auto& var = *opt; // a reference to the variant in the optional

            // use visit with the visitor helper `overloaded` to get the type
            // of input:
            std::visit(overloaded{
                           [](long long) { std::cout << "integer\n"; },
                           [](long double) { std::cout << "float\n"; },
                           [](const std::string&) { std::cout << "word\n"; },
                           [](const Line&) { std::cout << "line\n"; },
                       },
                       var);
        } else {
            std::cout << "Could not figure out what that was\n";
        }
    }
}

This would also work for user defined classes which can extract from istreams.

Demo

CodePudding user response:

Not exactly, but as I know you can use templates to do something similar, or you can create an object that receives the information and tests whether it's an integer, or a float, or string or char etc. and save on different temporal variables, not the most efficient way, use templates it's better but at least is a solution.

  •  Tags:  
  • c
  • Related