21

I know of const, that can't be changed after creation. But I was wondering if there is a way to declare a variable that you set only once and after that, can't overwrite. In my code, I would like to avoid the bool variable by having an nFirst that, once set to nIdx, can't be set to the new value of nIdx.

My code:

    int nFirst = 0;
    int nIdx = 0;
    bool bFound = false;
    BOOST_FOREACH(Foo* pFoo, aArray)
    {
        if (pFoo!= NULL)
        {
            pFoo->DoSmth();
            if (!bFound)
            {
                nFirst= nIdx;
                bFound = true;
            }
        }
        nIdx++;
    }
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
tzippy
  • 6,458
  • 30
  • 82
  • 151
  • 5
    None built-in AFAIK, but you can roll your own class that does this quite easily. – Alexander Torstling Mar 04 '16 at 16:09
  • No, it's not possible with standard features. – Eugene Sh. Mar 04 '16 at 16:09
  • 2
    You can create your own class for this. But the original boolean approach is what that class _will have to use_ too, so there is no difference in performance. – Hatted Rooster Mar 04 '16 at 16:09
  • 1
    `std::call_once`, `static` variables or simply checking if it's already set to nIdx. Yeah, it does not stop you from assigning it to something else by accidents somewhere else in your code, but your case doesn't seem like it need that anyway. –  Mar 04 '16 at 16:10
  • I also wish that C/C++ had a feature for making a variable `const` so that any code following (in program order in the current block) would not be able to modify it. Example: `Class1 x; if (expr) { Class2 obj(); x = obj.f(); } else { x.doStuff(); } const x; /* Code following sees x as const Class1 */`. – jotik Mar 04 '16 at 18:58

5 Answers5

19

Pretty easy to roll your own.

template<typename T>
class SetOnce
{
public:
    SetOnce(T init) : m_Val(init)
    {}

    SetOnce<T>& operator=(const T& other)
    {
        std::call_once(m_OnceFlag, [&]()
        {
            m_Val = other;
        });

        return *this;
    }

    const T& get() { return m_Val; }
private:
    T m_Val;
    std::once_flag m_OnceFlag;
};

Then just use the wrapper class for your variable.

SetOnce<int> nFirst(0);
nFirst= 1;
nFirst= 2;
nFirst= 3;

std::cout << nFirst.get() << std::endl;

Outputs:

1

lcs
  • 4,227
  • 17
  • 36
11

I would like to avoid the bool variable

You can check nFirst itself, based on the fact that it won't be set a negative number. Such as:

int nFirst = -1;
int nIdx = 0;
BOOST_FOREACH(Foo* pFoo, aArray)
{
    if (pFoo != NULL)
    {
        pFoo->DoSmth();
        if (nFirst == -1)
        {
            nFirst = nIdx;
        }
    }
    nIdx++;
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • 1
    @eugenesh any variable with a **const** prefix cannot be altered, eg. **const double pi=3.141592653;** – Arif Burhan Mar 04 '16 at 16:16
  • 2
    This seems like the most straight forward approach and definetly avoids the boolean. Thank you! – tzippy Mar 04 '16 at 16:18
  • also you can enum **enum bool{false,true}** **enum color{red=1,blue=16,green=256}** and you can have const enum or typedef enum. – Arif Burhan Mar 04 '16 at 16:26
  • @tzippy accept the answer if it helped you solve your issue. – stellarossa Mar 04 '16 at 17:04
  • 3
    If the number *can* be negative, you can choose some other value to be your uninitialized value, e.g. `std::numeric_limits::max()` or something. (If that's ever a legitimate value for your number, you probably shouldn't be using an `int` in the first place.) – Darrel Hoffman Mar 04 '16 at 18:19
2

Similar to cocarin's, but throws exception instead of silently ignoring assignment:

template <typename T, typename Counter = unsigned char>
class SetOnce {
public:
    SetOnce(const T& initval = T(), const Counter& initcount = 1):
        val(initval), counter(initcount) {}
    SetOnce(const SetOnce&) = default;
    SetOnce<T, Counter>& operator=(const T& newval) {
        if (counter) {
            --counter;
            val = newval;
            return *this;
        }
        else throw "Some error";
    }
    operator const T&() const { return val; } // "getter"
protected:
    T val;
    Counter counter;
};

Usage:

SetOnce<int> x = 42;
std::cout << x << '\n'; // => 42
x = 4;
// x = 5; // fails
std::cout << x << '\n'; // => 4

Online demo

Community
  • 1
  • 1
GingerPlusPlus
  • 5,336
  • 1
  • 29
  • 52
1

Your question is about avoiding the bool but also implies the need for const-ness.

To avoid the bool, I'd use a boost::optional like this:

boost::optional<int> nFirst;
// .. 
if (!nFirst) nFirst = nIdx;
// and now you can use *nFirst to get its value

Then, you can enforce logical (rather than literal) const-ness like this:

const boost::optional<int> nFirst;
// ..
if (!nFirst) const_cast<boost::optional<int>&>(nFirst) = nIdx;
// you can use *nFirst to get the value, any attempt to change it would cause a compile-time error

Using const_cast is not the safest practice, but in your particular case and as long as you only do it once it'd be OK. It simplifies both your code and your intentions: you do want a const, it's just that you want to defer it's initialisation for a bit.

Now, as songyuanyao suggested, you could directly use an int instead of a boost::optional, but the latter makes your intention explicit so I think it's better this way. In the end of day this is C++ while songyuanyao's solution is really a C-style one.

Marinos K
  • 1,779
  • 16
  • 39
0

This is set once template. You can use this class as assurance that the value will be set and saved only once. Every next try will be canceled.

#include <iostream>
using namespace std;
template <class T>
class SetOnce;
template<class T>
std::ostream& operator<<( ostream& os, const SetOnce<T>& Obj );

template <class T>
class SetOnce
{
public:
    SetOnce() {set = false; }
    ~SetOnce() {}
    void SetValue(T newValue) { value = !set ? newValue : value; set = true; }
private:
    T value;
    bool set;
    friend std::ostream& operator<< <>( ostream& os, const SetOnce& Obj );
public:
    SetOnce<T>& operator=( const T& newValue )
    {
        this->SetValue(newValue);
        return *this;
    }
};

template<class T>
std::ostream& operator<<( ostream& os, const SetOnce<T>& Obj )
{
    os << Obj.value;
    return os;
}

Use case:

int main()
{
    SetOnce<bool> bvar;
    SetOnce<int> ivar;
    SetOnce<std::string> strvar;

    std::cout<<"initial values: \n"<<bvar<<" "
    <<ivar<<" "<<strvar<<" \n\n";

    bvar = false;           //bvar.SetValue(false);
    ivar = 45;              //ivar.SetValue(45);
    strvar = "Darth Vader"; //strvar.SetValue("Darth Vader");

    std::cout<<"set values: \n"<<bvar<<" "
    <<ivar<<" "<<strvar<<" \n\n";

    bvar = true;        //bvar.SetValue(true);
    ivar = 0;           //ivar.SetValue(0);
    strvar = "Anakin";  //strvar.SetValue("Anakin");

    std::cout<<"set again values: \n"<<bvar<<" "
    <<ivar<<" "<<strvar<<" \n\n";

    return 0;
}