views:

253

answers:

4

How does one modify the following code to allow template function ask_runUI() to use s_EOF without making s_EOF public?

#include <string>
#include <iostream>
#include <sstream>
#include <vector>
class AskBase {
protected:
    std::string m_prompt;
    std::string m_answer; 
    virtual bool validate(std::string a_response) = 0;
public:
    AskBase(std::string a_prompt):m_prompt(a_prompt){}
    std::string prompt(){return m_prompt;}
    std::string answer(){return m_answer;}
    static int const s_EOF = -99;
    static int const s_BACKUP = -1;
    static int const s_OK = 1;
    int ask_user();
};
template<typename T> class Ask : public AskBase{
public:
    Ask(std::string a_prompt):AskBase(a_prompt){}
    bool validate(std::string a_response);
};
template<> bool Ask<std::string>::validate(std::string a_response){return true;}
template<> bool Ask<int>::validate(std::string a_response){int intAnswer;
    return (std::stringstream(a_response) >> intAnswer);}
int AskBase::ask_user(){
    for(;;){
        std::cout << "Enter " << m_prompt;
        std::string response;
        getline(std::cin, response);
        if (std::cin.eof())
            return s_EOF;
        else if (response == "^")
            return s_BACKUP;
        else if (validate(response)){
            m_answer = response;
            return s_OK;
        }
    }
    return s_EOF;
}
template<typename T> int ask_runUI(T& a_ui){
    int status = AskBase::s_OK;
    for (typename T::iterator ii=a_ui.begin();
            status!=AskBase::s_EOF && ii!=a_ui.end();
            ii+=(status==AskBase::s_BACKUP)? -1 : 1)
        status = (*ii)->ask_user();
    return (status == AskBase::s_OK);
}
int main(){
    std::vector<AskBase*> ui;
    ui.push_back(new Ask<std::string>("your name: "));
    ui.push_back(new Ask<int>("your age: "));
    if (ask_runUI(ui))
        for (std::vector<AskBase*>::iterator ii=ui.begin(); ii!=ui.end(); ++ii)
            std::cout << (*ii)->prompt() << (*ii)->answer() << std::endl;
    else
        std::cout << "\nEOF\n";
}
A: 

My kneejerk reaction is that AskBase would also need to be a template, and you would need to specify the template parameter in the friend declaration:

template <typename T>
class AskBase {
    AskBase(){};
    friend int ask_runUI (T);
...
};

But I admit there may be a better solution that I don't know about.

Dan Moulding
+2  A: 

If you want a template function to be a friend, you must say so in the class declaration. Change the line that declares the friend function to this:

template <typename T> 
friend int ask_runUI(T& a_ui);

Now, if your class is itself a template, things get a lot more complicated. Template friends are not trivial to do correctly. For that, I'll refer you to what C++ FAQ Lite says on the subject.

Kristo
A: 

The simplest is probably to replace static int const members with enumeration and not mess with friends:


class AskBase {
public:
    enum { Eof = -99, Null = 0, Ok = 1, Backup = -1 };
    ...
};
Nikolai N Fetissov
-1. I don't see how this changes anything -- your enum, which you have declared public, is in every material way equivalent to just making the static const int members public. If there is some distinction I've missed, please say and I'll reconsider.
j_random_hacker
The distinction is that compiler doesn't have to allocate space for static member variables. The advantage is that it's SIMPLE.
Nikolai N Fetissov
Well, I did some research and it's weirder than I thought. For one, if you simply declare and initialise the static const int members inside the class definition *without* defining them at namespace level (i.e. without writing "statict const int AskBase::s_Eof;" etc. at namespace level) then no space will ever be allocated. BUT, the compiler will only allow you to refer to s_Eof as an rvalue (e.g. binding to a non-const ref will fail at link time!!) See here: http://stackoverflow.com/questions/272900/c-undefined-reference-to-static-class-member, especially Richard Corden's answer.
j_random_hacker
+1  A: 

This worked for me!

class AskBase {
public:
    AskBase(){}
    template<typename T> 
    friend int ask_runUI(T& a_ui);
private:
    static int const    s_EOF = -99;
    static int const    s_BACKUP = -1;
    static int const    s_NULL = 0;
    static int const    s_OK = 1;
};
//int ask_runUI()
template<typename T> 
int ask_runUI(T& a_ui)
{
    return AskBase::s_NULL;
}
aJ
That's the classic anti-pattern for friending a template method. It is basically the same as making all members public as anybody can now write their own version of ask_runUI()
Martin York
They can, but will they to do so by accident, without realising that's what they're doing? Especially if ask_UI is in the same namespace as the class, if they go poking around inside a class's interface then surely they know they're breaking encapsulation?
Steve Jessop