Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
hooah
Feb 6, 2006
WTF?
Wait, what's this about vector<bool>? It's not just a regular STL vector containing bools?

Adbot
ADBOT LOVES YOU

sarehu
Apr 20, 2007

(call/cc call/cc)
No it's some special thing because somebody had to sperg out about sizeof(bool) being too big.

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
It's not, it's specialized as an array of bits. Bits are not addressable in C++, and the big thing about vector is that it's a type-safe and bounds-checked replacement of a variable-sized C array, with contiguous, addressable elements, compatible with pointer arithmetics on element addresses etc. (although this wasn't actually documented in the standard until recently). Everywhere you take a vector<T> you either have to code defensively and only use iterators, or specialize for vector<bool> and its limitations

It's basically an entire different class that's similar but not the same as vector

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

sarehu posted:

No it's some special thing because somebody had to sperg out about sizeof(bool) being too big.
It's actually quite a bit dumber than that: it's a specialization of vector<> rather than a separate data type specifically to show of how you can have specializations be completely different types.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Plorkyeran posted:

Making vector<bool> a normal container at this point would be a pretty painful breaking change for anything actually using it heavily, since the code using it would still compile and pass functional tests, but would suddenly start using 8 times as much memory and be way slower.

32 times on some platforms!

Sex Bumbo
Aug 14, 2004
E:nm

Sex Bumbo fucked around with this message at 18:09 on Jul 31, 2015

Joda
Apr 24, 2010

When I'm off, I just like to really let go and have fun, y'know?

Fun Shoe
I'm trying to refactor a messaging system I made a while ago to use templates in stead of having to define a new class for every type of data I want to send. I'm getting linker errors, though, and I'm not really sure why. I tried defining specifically what template instances it should generate but I get some error about incomplete types? If I define stuff in the header file I get an error about redifinitions of functions, even though I have a header guard. Can someone tell me what I'm doing wrong here?

Message.h
C++ code:
class Message {
public:
    Message();
    Message(messageType type);

    template<typename T>
    T extractMsgData();

    const messageType type;
    bool kill = false;
};

template<typename T>
class DataMessage : public Message {
public:
    DataMessage(messageType type, T data);

    T data;
};
Message.cpp
C++ code:
Message::Message(messageType type) :
type(type)
{

}

Message::Message() :
type(NO_TYPE)
{

}

template<typename T>
T Message::extractMsgData() {
    return static_cast<DataMessage<T>*>(this)->data;
}

template<typename T>
DataMessage<T>::DataMessage(messageType type, T data) :
Message(type),
data(data)
{
}

//specific templates
template class DataMessage<glm::vec2*>;
template class DataMessage<glm::vec3>;
template class DataMessage<glm::vec2>;
template class DataMessage<std::string>;
template class DataMessage<std::string*>;
template class DataMessage<float>;
E: Also, does someone know how I can do either compile time or runtime checks of the DataMessage cast? I want to check if the type I'm casting to is actually what is at the pointer address, in stead of it giving me a non-descript runtime error if I try to ectract something other than what the message contains.

Joda fucked around with this message at 18:46 on Aug 2, 2015

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
You can't define templates out-of-line in an implementation file like that; they need to be defined in a header that's included by all the places that use them. (I strongly recommend against trying to fix the problem with explicit instantiations. That way lies endless heartbreak.)

Sex Bumbo
Aug 14, 2004
It might sound dumb, but short of changing your approach to messages, having an enum to store the type might be preferable.

Consider: If you pass a message pointer around, any strategy that involves polymorphism is going to hit a lookup into a vtable. E.g. you could use a dynamic_cast to make sure you're casting into something valid, which is probably what you were asking for. This involves a type check which, best case, isn't going to be much different than an enum comparison and worst case will have to fetch memory likely outside of cache.

Or to put another way, if you were implementing this in assembly, there's no real way you can have a type-checked homogenous message that's compile time safe or runtime safe without passing extra data around.

Sex Bumbo fucked around with this message at 01:56 on Aug 3, 2015

Joda
Apr 24, 2010

When I'm off, I just like to really let go and have fun, y'know?

Fun Shoe
Alright, thanks. I tried implementing the functions in the header and now it works for some reason. Does implementing non-member functions in headers cause problems with multiple includes or something, because that is the only difference from last time I tried.



Sex Bumbo posted:

It might sound dumb, but short of changing your approach to messages, having an enum to store the type might be preferable.

Consider: If you pass a message pointer around, any strategy that involves polymorphism is going to hit a lookup into a vtable. E.g. you could use a dynamic_cast to make sure you're casting into something valid, which is probably what you were asking for. This involves a type check which, best case, isn't going to be much different than an enum comparison and worst case will have to fetch memory likely outside of cache.

Or to put another way, if you were implementing this in assembly, there's no real way you can have a type-checked homogenous message that's compile time safe or runtime safe without passing extra data around.

messageType is already an enum in Message.h that contains all the types of messages I might want to send around. Every type has a specific type of data that I intend to send with it, but this is not enforced and I'm not sure if/how I can do that. I'm pretty sure it won't be a problem for me, but part of me feels like it's against good coding practice or something, since nothing is stopping someone from sending, say, a WINDOW_RESIZE_MSG (which is meant to get a vec2) as DataMessage<std::string>, or have the receiver try and extract the data with extractMsgData<float>() or something.

E: I was looking into dynamic_cast, but my understanding was that it only works from subclass to superclass and not the other way around?

Joda fucked around with this message at 02:45 on Aug 3, 2015

Sex Bumbo
Aug 14, 2004
You can return an error somehow if they try to get the wrong type, but there's really no way to know at compile time the derived type of a base pointer. The templates are nice because it hides an ugly cast.

Sex Bumbo fucked around with this message at 02:47 on Aug 3, 2015

Sex Bumbo
Aug 14, 2004

Joda posted:

E: I was looking into dynamic_cast, but my understanding was that it only works from subclass to superclass and not the other way around?


I never use dynamic cast but here's what I was referring to:
http://cpp.sh/3oki

So if it dynamic casts into null, you know you've got the wrong type. But it's not a fabulous way to do things.

I kinda glossed over that you have message type info too... at that point, it's kind of redundant isn't it? You have both a v-table, which you need if you want to clean up strings, but also the type info, which means you can just call the string destructor manually if you wanted to.

Sex Bumbo fucked around with this message at 03:15 on Aug 3, 2015

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Joda posted:

Alright, thanks. I tried implementing the functions in the header and now it works for some reason. Does implementing non-member functions in headers cause problems with multiple includes or something, because that is the only difference from last time I tried.

Yes, but it's not really about member functions. Function definitions in C++ can be "inline" or not. Crucial note: if you've ever heard of the "inlining" compiler optimization, this "inline" has absolutely nothing to do with that, although that is a super-common misunderstanding. You should think of "inline" as just a word with a specialized meaning, the same way that "static" is just a word and it's best not to think too hard about it. As you can see, C and C++ are full of words like that. (Programming in general, really.)

The special thing about an inline definition is that it can be defined multiple times in a program. Recall that C and C++ are built on a model of textual inclusion: if you define a function in a header file, and then include that header in multiple translation units, then it's really as if you'd written out that function definition in multiple places. In contrast, a non-inline function can be defined at most once. The caveat is that, if a function has an inline definition, that definition has to appear in every translation unit that uses the function.

Three main things can make a function definition inline: declaring it "inline" explicitly, making it a template (or part of one), and defining it inside a class body.

So you usually can't define templated functions in a non-header file because they're always implicitly inline, which means they have to be defined in every translation unit that uses them. (Explicit instantiations create an exception to this rule, but they introduce a lot of other problems; it's best to ignore them until you're really stuck.) And conversely, you can't define just any old function in a header file without declaring it "inline" explicitly, because a non-inline function definition can appear in at most one translation unit.

If this all sounds crazy, it is; C++ really suffers as a language from trying to layer such a rich set of abstraction tools on top of the C textual-inclusion model.

Paniolo
Oct 9, 2007

Heads will roll.

Joda posted:

E: I was looking into dynamic_cast, but my understanding was that it only works from subclass to superclass and not the other way around?

It would have no reason to exist if that were the case, since C++ will already implicitly cast a subclass to a superclass.

Dynamic_cast is effectively a checked static_cast that will throw an exception on failure (whereas static_cast will give you a garbage pointer.)

I would suggest looking into the visitor pattern. It tends to be a more elegant solution to type-based switching in C++.

feedmegin
Jul 30, 2008

dynamic_cast gives you a null pointer (when used with a pointer) not an exception.

ullerrm
Dec 31, 2012

Oh, the network slogan is true -- "watch FOX and be damned for all eternity!"

Paniolo posted:

Dynamic_cast is effectively a checked static_cast that will throw an exception on failure (whereas static_cast will give you a garbage pointer.)

Dynamic_cast's behavior actually depends on the target type.

If the requested type is a pointer type, it returns a null pointer, with the type of the requested type.
If the requested type is a reference type, it throws std::bad_cast.

(There's also a very obscure use -- dynamic_cast<void *>(expression) is supposed to return a pointer to the most derived object that expr points to. This can be useful for determining whether two interfaces point to the same underlying object, similar to IUnknown on Windows.)

e:f,b, should hit F5 first

Hyvok
Mar 30, 2010
Maybe slightly off-topic (but related to C/C++ compilers, CGAL and cmake) how does auto-linking work?

I'm currently trying to include CGAL in my C++ project with cmake and I've gotten it so far that it compiles and links and seems to work IF I manually copy the "CGAL-vc120-mt-gd-4.6.1.dll" with my executable. The problem is that as far as I can see that dll/library name is NOT specified by the cmake scripts (CGAL_LIBRARIES is just empty). Looking at the compiler invocation the only thing given to the compiler (MSVC) is -LIBPATH to CGAL lib folder which contains "CGAL-vc120-mt-gd-4.6.1.lib". Where the hell does the compiler figure out its that exact file it needs for linking and more importantly how do I figure it out within cmake so I can copy it (well, the same name file with .dll ending) with my executable? I'm really hoping I'm not supposed to somehow parse/generate that name myself. I kinda know what all the letters in that lib name mean (same naming as in boost) but I'd like to avoid doing that...

robostac
Sep 23, 2009
Having had a quick look, it seems like CGAL builds up that string in include/CGAL/auto_link/auto_link.h, then links to it using #pragma(lib ...) which is a visual studio specific way of specifying libraries from source files. It looks like if you define CGAL_LIB_DIAGNOSTIC it would print out the name while compiling, which might be enough for you?

raminasi
Jan 25, 2005

a last drink with no ice
You might also be able to save yourself this headache (and other headaches) by just statically linking CGAL.

Hyvok
Mar 30, 2010
Looks like trying to understand how to use the CGAL library even for very simple things seems pretty daunting (there seems to be a fair bit of docs, but I just don't really seem to understand what they are talking about...) so I'm thinking maybe it is a bit of a rocket launcher to squash a fly.

Does anyone have any recommendations for a simple as possible library for creating 2D triangulations with constraints/holes? The only thing I'd need is just to be able to give a bunch of points and say these make up the outline (as a polyline), and then POSSIBLY give a bunch of points (as a polyline) and say these form a hole within the geometry and then :gizz: out would come a bunch of triangles. Header only would be even more awesome. All the options (Triangle, poly2tri, CGAL, Fade2D) I've so far looked at seem really complex/convoluted, have around 1 lines of documentation or have some prohibitive license (Fade2D, which seemed like the best of the bunch...) for such a simple task. For example first line on first CGAL example is "typedef CGAL::Exact_predicates_inexact_constructions_kernel K;" without any explanation what it even is :wtc:

Hyvok fucked around with this message at 18:54 on Aug 6, 2015

raminasi
Jan 25, 2005

a last drink with no ice

Hyvok posted:

Looks like trying to understand how to use the CGAL library even for very simple things seems pretty daunting (there seems to be a fair bit of docs, but I just don't really seem to understand what they are talking about...) so I'm thinking maybe it is a bit of a rocket launcher to squash a fly.

Does anyone have any recommendations for a simple as possible library for creating 2D triangulations with constraints/holes? The only thing I'd need is just to be able to give a bunch of points and say these make up the outline (as a polyline), and then POSSIBLY give a bunch of points (as a polyline) and say these form a hole within the geometry and then :gizz: out would come a bunch of triangles. Header only would be even more awesome. All the options (Triangle, poly2tri, CGAL, Fade2D) I've so far looked at seem really complex/convoluted, have around 1 lines of documentation or have some prohibitive license (Fade2D, which seemed like the best of the bunch...) for such a simple task. For example first line on first CGAL example is "typedef CGAL::Exact_predicates_inexact_constructions_kernel K;" without any explanation what it even is :wtc:

I don't know about other triangulation libraries, but I spent three years using CGAL, so if you end up going ahead with it just post here and I can try to help you out. You're absolutely right that it's totally nuclear powered, and you cannot just dive right in.

Basically, CGAL's gimmick is that it uses exact computation, which means that it's slow and somewhat constrained, but you never (literally never) have numerical stability problems. In order to manage this tradeoff, CGAL makes you pay a bunch of attention to the mathematical properties of the numbers and algorithms you're using. It's a pretty big pain in the rear end to set up, but once you've got it going, it's nice to not have to worry that your program will wander into some floating-point corner-case and crash. (I don't actually think the tradeoff is worth it, but we had to do it because CGAL was the only library that implemented an algorithm we needed.)

Spatial
Nov 15, 2007

Maybe give polypartition a try. It's very simple to use, produces good results, and has a permissive license.

Vanadium
Jan 8, 2005

Is iconv broken in glibc or am I just dumb?

http://codepad.org/gejDycP7

The Laplace Demon
Jul 23, 2009

"Oh dear! Oh dear! Heisenberg is a douche!"

Vanadium posted:

Is iconv broken in glibc or am I just dumb?

http://codepad.org/gejDycP7

Considering any byte above 0xFD is invalid UTF-8, and you are explicitly setting a byte in "UTF-8" text to 0xFF...

EDIT: Or 0xF7 according to RFC3629.

EDIT2:
VVVVV
Err, I got the arguments swapped. Yes 0xFF isn't valid ASCII, but "//IGNORE" only prevents against invalid characters in the output charset. 0xFF is an invalid character in the input charset, hence "broken" output.

The Laplace Demon fucked around with this message at 09:26 on Aug 10, 2015

Vanadium
Jan 8, 2005

No, I'm setting a byte to a non-ASCII value and asking iconv interpret the input as ASCII while skipping invalid sequences. The problem is that it ignores a bunch of other stuff as well, seemingly related to the size of the output buffer.

pseudorandom name
May 6, 2007

Vanadium posted:

No, I'm setting a byte to a non-ASCII value and asking iconv interpret the input as ASCII while skipping invalid sequences. The problem is that it ignores a bunch of other stuff as well, seemingly related to the size of the output buffer.

That's not what //IGNORE means.

ullerrm
Dec 31, 2012

Oh, the network slogan is true -- "watch FOX and be damned for all eternity!"

Vanadium posted:

No, I'm setting a byte to a non-ASCII value and asking iconv interpret the input as ASCII while skipping invalid sequences. The problem is that it ignores a bunch of other stuff as well, seemingly related to the size of the output buffer.

Two things:

* You need to do an initial call to iconv() with a NULL inptr to indicate that you need to initialize its state machine. (See the iconv manual, and the example code in the glibc manual. Grep for "/* Now write out the byte sequence" to get to the relevant line.)

* I suspect that you may be looking at old data from previous runs. In your implementation of conv(), zero out the contents of outbuf before calling iconv(), and be sure to print the contents of errno and inbytesleft/outbytesleft.

pseudorandom name
May 6, 2007

ullerrm posted:

Two things:

* You need to do an initial call to iconv() with a NULL inptr to indicate that you need to initialize its state machine. (See the iconv manual, and the example code in the glibc manual. Grep for "/* Now write out the byte sequence" to get to the relevant line.)

Nah, it starts in the initial state at iconv_open() time and will do whatever initial output is necessary at the first call to iconv(), the example is just being really explicit.

ullerrm posted:

* I suspect that you may be looking at old data from previous runs. In your implementation of conv(), zero out the contents of outbuf before calling iconv(), and be sure to print the contents of errno and inbytesleft/outbytesleft.

This is probably the actual problem, everything related to buffer output is a confused mess that I didn't even bother to understand.

c.f. http://ideone.com/zfQOHP

Vanadium
Jan 8, 2005


It only happens in subsequent calls to iconv, when the converted text doesn't fit into the output buffer all at once.

If you wanna see it with buffer handling that I am innocent of, try

(head -c 32768 /dev/zero; echo $'A\xffB') | iconv -c -f ascii

and notice how there's a B in the output but not an A. (32768 is the output buffer size in glibc's iconv program.)

Newf
Feb 14, 2006
I appreciate hacky sack on a much deeper level than you.
I've got an MFC CEdit box with a configured ON_EN_CHANGE event. I'd like this event to fire only when the text changes due to user input, but it's currently firing when the text is changed by, for example, editCtrl.SetWindowText("asof");. Any suggestions? My first idea is to check in the event handler whether the CEdit in question has focus and fire if it does. This seems wrong...?

ullerrm
Dec 31, 2012

Oh, the network slogan is true -- "watch FOX and be damned for all eternity!"

Newf posted:

I've got an MFC CEdit box with a configured ON_EN_CHANGE event. I'd like this event to fire only when the text changes due to user input, but it's currently firing when the text is changed by, for example, editCtrl.SetWindowText("asof");. Any suggestions? My first idea is to check in the event handler whether the CEdit in question has focus and fire if it does. This seems wrong...?

Yes, that could be wrong; in theory, the text could change while the user is typing. Also, beware of accessibility programs that do not always predictably swap focus.

The proper solution -- or, at least, close enough -- is to define a flag, set it before calling SetWindowText, and clear it afterward. In your ON_EN_CHANGE handler, check that flag, and do nothing if it's set. (You should probably consider making a subclass of CEdit to implement this.)

Are you doing this to filter out invalid user input? If so, you might want to alternately consider subclassing CEdit and overriding the keyboard messages of CWnd (OnKeyUp, OnKeyDown, OnChar, etc.) so that it can't send invalid keystrokes to the underlying CEdit.

e: VVVV or that, yeah -- do your validation once focus leaves the edit control.

ullerrm fucked around with this message at 20:48 on Aug 11, 2015

High Protein
Jul 12, 2009
Perhaps consider triggering your event when the focus leaves the edit control; doing stuff while the user is typing often results in annoying behavior.

Newf
Feb 14, 2006
I appreciate hacky sack on a much deeper level than you.
Thanks for the inputs - I'll work something out. I'll keep talking mostly in a duck-debugging sense.

The Cedit is displaying a (user editable) CString member of some object (the name, for example), and I'm having the change event both write to the in-memory object in question and also trigger the 'SetModified' method of the PropertyPage so that the 'Apply' button comes active when they make changes. 'Apply' writes the in-memory object to the registry.

So the problem with waiting until focus is lost is that users who click the box and type something into it won't have the 'Apply' button available to them. (Until after they tab or click out of it).

The problem with triggering it whenever it changes is that the object in question is a member of an array, so when the user selects a different member of the array, the edit is changed to that other object's value, which then incorrectly calls 'SetModified' and enables the 'Apply' button.

I guess I'll just go with a flag that's set around the user's switching between objects.

Ika
Dec 30, 2004
Pure insanity

ullerrm posted:

Yes, that could be wrong; in theory, the text could change while the user is typing. Also, beware of accessibility programs that do not always predictably swap focus.

The proper solution -- or, at least, close enough -- is to define a flag, set it before calling SetWindowText, and clear it afterward. In your ON_EN_CHANGE handler, check that flag, and do nothing if it's set. (You should probably consider making a subclass of CEdit to implement this.)

Are you doing this to filter out invalid user input? If so, you might want to alternately consider subclassing CEdit and overriding the keyboard messages of CWnd (OnKeyUp, OnKeyDown, OnChar, etc.) so that it can't send invalid keystrokes to the underlying CEdit.

e: VVVV or that, yeah -- do your validation once focus leaves the edit control.

The initial behavior is on of the most irritating things about win32 controls, just about every other control doesn't send notifications when its being changed programmatically.

What I tend to do is store the control ID of the edit control I am currently changing the text in in a variable, so I only need one var for all the notifications in an entire dialog and if I have to worry about recursion/reentrancy I can cache the old values and restore them when returning from the handler.

omeg
Sep 3, 2012

The whole win32 windowing system is a gigantic hack. On the topic, anyone knows how to programmatically detect when a given window is flashing (caused by FlashWindowEx)? No messages seem to be sent in this case, it seems to be implemented by some internal win32 timer.

ullerrm
Dec 31, 2012

Oh, the network slogan is true -- "watch FOX and be damned for all eternity!"

omeg posted:

The whole win32 windowing system is a gigantic hack. On the topic, anyone knows how to programmatically detect when a given window is flashing (caused by FlashWindowEx)? No messages seem to be sent in this case, it seems to be implemented by some internal win32 timer.

It's not particularly performant, but: Use SetWindowsHookEx() to set a message hook for WH_SHELL. In the hook, check for nCode == HSHELL_REDRAW && lParam == TRUE.

You will need admin privileges to do this for any window not owned by your process, since hooking the message pump for another process involves injecting a module. (If you do have the privs, however, the set-hook API will do it for you.)

ullerrm fucked around with this message at 19:30 on Aug 13, 2015

omeg
Sep 3, 2012

ullerrm posted:

It's not particularly performant, but: Use SetWindowsHookEx() to set a message hook for WH_SHELL. In the hook, check for nCode == HSHELL_REDRAW && lParam == TRUE.

Ah right, shell hooks... Thanks, will give that a try.

Hyvok
Mar 30, 2010
I ran in to a stupid problem and due to being stupid I can't see a very simple solution for it.

I basically have two lists of the same type, the other list is const and the other one is not. I have a struct which contains an iterator for those lists and some other related stuff. With MSVC it was fine that the iterator was const, because it allowed me to still erase stuff from the non-const list with the const_iterator. But gcc does not allow me to do that (apparently GCC 4.9.2 will allow that with C++11 flag). I also have some functions that take in the struct.

If I create two structs, the other with a const iterator and the other with a non-const one then I need overloads for all the functions. If I use a template for selecting the iterator type then I still need the overloads because apparently the compiler can't convert a blaastruct<blaablaa::iterator> to blaastruct<blaablaa::const_iterator> implicitly. Am I not seeing some simple solution?

There seems to be a way to rid the const_iterator of constness but its a bit of a hack... Edit: Actually this doesn't work either because it relies on the same not implemented feature of being able to call erase with a const_iterator, duh http://stackoverflow.com/questions/765148/how-to-remove-constness-of-const-iterator

Hyvok fucked around with this message at 09:27 on Aug 15, 2015

sarehu
Apr 20, 2007

(call/cc call/cc)

Hyvok posted:

If I use a template for selecting the iterator type then I still need the overloads because apparently the compiler can't convert a blaastruct<blaablaa::iterator> to blaastruct<blaablaa::const_iterator> implicitly. Am I not seeing some simple solution?

Something like

code:
template <class T>
struct blaastruct {
  T field;

  template <class U>
  blaastruct(const blaastruct<U>& copyee) : field(copyee.field) { }
};
Maybe also for a move constructor if that would be useful.

Adbot
ADBOT LOVES YOU

Olly the Otter
Jul 22, 2007

Hyvok posted:

I basically have two lists of the same type, the other list is const and the other one is not. I have a struct which contains an iterator for those lists and some other related stuff.

I've found boost::intrusive::list to be useful in this sort of situation. The idea is that instead of storing an iterator in your struct, you store an ordinary pointer. When you need an iterator (for example, when deleting the element), you use the iterator_to method. iterator_to is constant-time due to the nature of intrusive lists -- the item itself contains information about its location in the list.

The drawback is you have to modify the definition of the items you're storing in the list, which can be a pain in the neck or no problem at all depending on the use case.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply